├── .classpath ├── .gitignore ├── .project ├── AndroidManifest.xml ├── LICENSE.txt ├── README.txt ├── default.properties ├── res ├── drawable-hdpi │ └── icon.png ├── drawable-ldpi │ ├── arrow.png │ ├── arrowred.png │ ├── icon.png │ └── stairs.png ├── drawable-mdpi │ ├── arrow.png │ ├── arrowred.png │ └── icon.png ├── layout │ ├── calibrator.xml │ ├── displayroute.xml │ └── selectroom.xml ├── values │ └── strings.xml └── xml │ ├── map1og.osm │ ├── map1ug.osm │ ├── map2og.osm │ ├── map3og.osm │ ├── mapeg.osm │ └── parkinglot.osm ├── src └── de │ └── uvwxy │ ├── footpath │ ├── ToolBox.java │ ├── core │ │ ├── NPConfig.java │ │ ├── Positioner.java │ │ ├── Positioner_OnlineBestFit.java │ │ ├── Positioner_OnlineFirstFit.java │ │ ├── StepDetection.java │ │ └── StepTrigger.java │ ├── graph │ │ ├── Graph.java │ │ ├── GraphEdge.java │ │ ├── GraphElement.java │ │ ├── GraphNode.java │ │ ├── GraphWay.java │ │ └── LatLonPos.java │ ├── gui │ │ ├── Calibrator.java │ │ ├── Loader.java │ │ ├── Navigator.java │ │ ├── PaintBoxHistory.java │ │ ├── PaintBoxMap.java │ │ └── Tile.java │ └── log │ │ ├── AudioWriter.java │ │ ├── DataLogger.java │ │ └── FWriter.java │ └── paintbox │ ├── PaintBox.java │ └── PaintThread.java ├── tools ├── 4228_4222.png ├── 4228_5056.png ├── README └── URL2QR.jar └── update-revfile.sh /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /gen/ 3 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | FootPathMaster 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | FootPath (Infrastructureless indoor navigation for smart phones) 2 | 3 | Copyright (C) 2010-2011, Paul Smith 4 | Copyright (C) 2010-2011, Jó Ágila Bitsch 5 | Copyright (C) 2010-2011, Chair of Computer Science 4, RWTH Aachen University, 6 | 7 | 8 | 9 | FREE SOFTWARE 10 | ############# 11 | 12 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License along with this program. If not, see . 17 | 18 | IF YOU NEED ANOTHER LICENSE 19 | ########################### 20 | 21 | If you are planning to integrate FootPath into a commercial product, please contact us for licensing options via email at: 22 | 23 | jo.bitsch@cs.rwth-aachen.de 24 | 25 | 26 | IF YOU REALLY LIKE OUR SOFTWARE 27 | ############################### 28 | 29 | Buy us a beer when the situation arises :-) 30 | 31 | 32 | OTHER ISSUES 33 | ############ 34 | 35 | This software is currently a proof of concept. It needs some more work to make it generally useable for a non technical person. 36 | 37 | Open issues include: 38 | * Automatically downloading available indoor maps 39 | * Better UI 40 | * Looking at several possible paths at once. 41 | 42 | 43 | HOW TO USE THIS SOFTWARE 44 | ######################## 45 | * Create your maps with JOSM and put them into the folder res/xml 46 | * NOTE: ways and nodes should have an attribute indoor=yes and a name 47 | * compare with our maps. (TODO) 48 | * Change lines 199-204 in Loader.java to load your maps on startup 49 | * Run update-revfile.sh after every commit, so that log messages refer to the correct revision. 50 | -------------------------------------------------------------------------------- /default.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "build.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-8 12 | -------------------------------------------------------------------------------- /res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /res/drawable-ldpi/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/res/drawable-ldpi/arrow.png -------------------------------------------------------------------------------- /res/drawable-ldpi/arrowred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/res/drawable-ldpi/arrowred.png -------------------------------------------------------------------------------- /res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /res/drawable-ldpi/stairs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/res/drawable-ldpi/stairs.png -------------------------------------------------------------------------------- /res/drawable-mdpi/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/res/drawable-mdpi/arrow.png -------------------------------------------------------------------------------- /res/drawable-mdpi/arrowred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/res/drawable-mdpi/arrowred.png -------------------------------------------------------------------------------- /res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /res/layout/calibrator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /res/layout/displayroute.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /res/layout/selectroom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 12 | 15 | 19 | 22 | 26 | 30 | 34 | 38 | 42 | 46 | 50 | 51 | 55 | 58 | 61 | 64 | 68 | 69 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World, Loader! 4 | FootPath 5 | 6 | -------------------------------------------------------------------------------- /res/xml/map1ug.osm: -------------------------------------------------------------------------------- 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 | 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 | 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 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | -------------------------------------------------------------------------------- /res/xml/parkinglot.osm: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/ToolBox.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath; 2 | 3 | import java.util.ArrayList; 4 | 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Paint.Style; 8 | 9 | /** 10 | * 11 | * @author Paul Smith 12 | * 13 | */ 14 | public class ToolBox { 15 | public static float strokeWidth = 2.0f; 16 | 17 | public static Paint redPaint() { 18 | Paint p = new Paint(); 19 | p.setStrokeWidth(strokeWidth); 20 | p.setStyle(Style.FILL); 21 | p.setColor(Color.RED); 22 | return p; 23 | } 24 | 25 | public static Paint redPaint(float textSize) { 26 | Paint p = new Paint(); 27 | p.setTextSize(textSize); 28 | p.setStrokeWidth(strokeWidth); 29 | p.setStyle(Style.FILL); 30 | p.setColor(Color.RED); 31 | return p; 32 | } 33 | 34 | public static Paint greenPaint() { 35 | Paint p = new Paint(); 36 | p.setStrokeWidth(strokeWidth); 37 | p.setStyle(Style.FILL); 38 | p.setColor(Color.GREEN); 39 | return p; 40 | } 41 | 42 | public static Paint greenPaint(float textSize) { 43 | Paint p = new Paint(); 44 | p.setTextSize(textSize); 45 | p.setStrokeWidth(strokeWidth); 46 | p.setStyle(Style.FILL); 47 | p.setColor(Color.GREEN); 48 | return p; 49 | } 50 | 51 | public static Paint bluePaint() { 52 | Paint p = new Paint(); 53 | p.setStrokeWidth(strokeWidth); 54 | p.setStyle(Style.FILL); 55 | p.setColor(Color.BLUE); 56 | return p; 57 | } 58 | 59 | public static Paint transparentBluePaint() { 60 | Paint p = new Paint(); 61 | p.setStrokeWidth(strokeWidth); 62 | p.setStyle(Style.STROKE); 63 | p.setColor(Color.BLUE); 64 | return p; 65 | } 66 | 67 | public static Paint bluePaint(float textSize) { 68 | Paint p = new Paint(); 69 | p.setTextSize(textSize); 70 | p.setStrokeWidth(strokeWidth); 71 | p.setStyle(Style.FILL); 72 | p.setColor(Color.BLUE); 73 | return p; 74 | } 75 | 76 | 77 | public static Paint myPaint(int strokeWidth, int color) { 78 | Paint p = new Paint(); 79 | p.setStrokeWidth(strokeWidth); 80 | p.setStyle(Style.FILL); 81 | p.setColor(color); 82 | return p; 83 | } 84 | 85 | public static Paint myPaint(int strokeWidth, int color, int alpha) { 86 | Paint p = new Paint(); 87 | p.setStrokeWidth(strokeWidth); 88 | p.setStyle(Style.FILL); 89 | p.setColor(color); 90 | p.setAlpha(alpha); 91 | return p; 92 | } 93 | 94 | /** 95 | * Creates a normal double array out of an ArrayList. 96 | * 97 | * @param al 98 | * the ArrayList to convert 99 | * @return the double[] array made out of array list 100 | */ 101 | public static double[] arrayListToArrayDouble(ArrayList al) { 102 | // create array with appropriate size 103 | double[] a = new double[al.size()]; 104 | for (int i = 0; i < al.size(); i++) { 105 | // fill it 106 | a[i] = al.get(i).doubleValue(); 107 | } 108 | // voila 109 | return a; 110 | } 111 | 112 | /** 113 | * Creates a normal int array out of an ArrayList. 114 | * 115 | * @param al 116 | * the ArrayList to convert 117 | * @return the int[] array made out of array list 118 | */ 119 | public static int[] arrayListToArrayInt(ArrayList al) { 120 | // create array with appropriate size 121 | int[] a = new int[al.size()]; 122 | for (int i = 0; i < al.size(); i++) { 123 | // fill it 124 | a[i] = al.get(i).intValue(); 125 | } 126 | // voila 127 | return a; 128 | } 129 | 130 | // precision to two decimal places behind . 131 | public static double tdp(double d) { 132 | return ((int) (d * 100)) / 100.0; 133 | } 134 | 135 | public static double[] arrayClone(double[] array) { 136 | double[] buf = new double[array.length]; 137 | for (int i = 0; i < array.length; i++) { 138 | buf[i] = array[i]; 139 | } 140 | return buf; 141 | } 142 | 143 | 144 | public static double lowpassFilter(double old_value, double new_value, double a) { 145 | return old_value + a * (new_value - old_value); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/core/NPConfig.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.core; 2 | 3 | /** 4 | * A class to manage a configuration of the navigation 5 | * @author Paul Smith 6 | * 7 | */ 8 | public class NPConfig{ 9 | // Points to the current edge we are walking on 10 | public int npPointer; 11 | // How far we have come on this edge 12 | public double npCurLen; 13 | // How many unmatched steps we have since the last matched step 14 | public int npUnmatchedSteps; 15 | // Which step the last matched step is 16 | public int npLastMatchedStep; 17 | // How many total matched steps we have 18 | public int npMatchedSteps; 19 | // The step size 20 | public double npStepSize; 21 | 22 | public NPConfig(){ 23 | this.npPointer = 0; 24 | this.npCurLen = 0.0; 25 | this.npUnmatchedSteps = 0; 26 | this.npLastMatchedStep = 0; 27 | this.npMatchedSteps = 0; 28 | this.npStepSize = 1.0; // 1.0m... 29 | } 30 | 31 | public NPConfig(NPConfig conf) { 32 | this.npCurLen = conf.npCurLen; 33 | this.npLastMatchedStep = conf.npLastMatchedStep; 34 | this.npMatchedSteps = conf.npMatchedSteps; 35 | this.npPointer = conf.npPointer; 36 | this.npStepSize = conf.npStepSize; 37 | this.npUnmatchedSteps = conf.npUnmatchedSteps; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/core/Positioner.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.core; 2 | 3 | /** 4 | * 5 | * @author Paul Smith 6 | * 7 | */ 8 | public abstract class Positioner { 9 | public abstract void addStep(double direction); 10 | public abstract double getProgress(); 11 | 12 | /** 13 | * Check if the difference of the given angles in degrees is less than the given alowed difference 14 | * @param v the first angle 15 | * @param t the second angle 16 | * @param diff the allowed difference 17 | * @return true if v <= diff away from t 18 | */ 19 | public static boolean isInRange(double v, double t, double diff) { 20 | if(Math.abs(v-t)<=diff){ 21 | return true; 22 | } 23 | if(Math.abs((v+diff)%360-(t+diff)%360)<=diff){ 24 | return true; 25 | } 26 | return false; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/core/Positioner_OnlineBestFit.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.core; 2 | 3 | import java.util.LinkedList; 4 | 5 | import de.uvwxy.footpath.graph.GraphEdge; 6 | import de.uvwxy.footpath.gui.Navigator; 7 | 8 | /** 9 | * A class calculating the position concerning Best Fit 10 | * 11 | * @author Paul Smith 12 | * 13 | */ 14 | public class Positioner_OnlineBestFit extends Positioner{ 15 | private Navigator nav = null; 16 | private NPConfig conf = null; 17 | private LinkedList edges = null; 18 | private double[][] c = null; 19 | private double all_dist = 0.0; 20 | private double avg_steplength = 0.0; 21 | private double[] from_map = null; 22 | private LinkedList s = null; 23 | private double[][] dyn = null; 24 | private final int INITIAL_DYN_SIZE = 2; 25 | private int currentStep = 0; 26 | private double progress = 0.0; 27 | 28 | public Positioner_OnlineBestFit(Navigator nav, LinkedList edges, NPConfig conf){ 29 | this.nav = nav; 30 | this.edges = edges; 31 | this.conf = conf; 32 | 33 | c = new double[edges.size()][2]; 34 | // Setup c: 35 | double tempLen = 0.0; 36 | for(int i = 0; i < edges.size(); i++){ 37 | GraphEdge temp = edges.get(i); 38 | tempLen+=temp.getLen(); 39 | c[i][0] = tempLen; 40 | c[i][1] = temp.getCompDir(); 41 | } 42 | 43 | // Setup all_dist: 44 | all_dist = c[edges.size()-1][0]; 45 | 46 | // Setup avg_steplength: 47 | this.avg_steplength = nav.getStepLengthInMeters(); 48 | 49 | // Setup n: 50 | double[] n = new double[(int)(all_dist/this.avg_steplength)]; 51 | for(int i = 0; i < n.length; i++){ 52 | n[i] = this.avg_steplength*i; 53 | } 54 | 55 | // Setup from_map: 56 | from_map = new double[n.length]; 57 | for(int i = 0; i < n.length; i++){ 58 | // This code below uses directions directly from edges 59 | int edge_i = 0; 60 | while(!(c[edge_i][0] > n[i])){ 61 | edge_i++; 62 | } 63 | from_map[i]=c[edge_i][1]; 64 | } 65 | 66 | // s: a list to store detected step headings 67 | s = new LinkedList(); 68 | 69 | // dyn: the last two lines from the matrix D 70 | dyn = new double[INITIAL_DYN_SIZE][from_map.length+1]; 71 | 72 | // initialization 73 | for(int x = 0; x < INITIAL_DYN_SIZE; x++){ 74 | for(int y = 0; y < dyn[0].length; y++){ 75 | if ( x == 0 && y == 0 ){ 76 | dyn[x][y] = 0.0; 77 | } else if (x == 0){ 78 | dyn[x][y] = Double.POSITIVE_INFINITY; 79 | } else if (y == 0){ 80 | dyn[x][y] = Double.POSITIVE_INFINITY; 81 | } 82 | // lens[x][y] = 0.0; 83 | } 84 | } 85 | 86 | 87 | } 88 | 89 | boolean firstStep = false; 90 | 91 | public void addStep(double direction){ 92 | if(firstStep){ 93 | dyn[0][0] = Double.POSITIVE_INFINITY; 94 | } 95 | firstStep = true; 96 | 97 | double t1,t2,t3; 98 | 99 | currentStep++; 100 | int x = currentStep; 101 | 102 | s.add(new Double(direction)); 103 | 104 | // calculate new line of the matrix D: 105 | for(int y = 1; y < dyn[0].length; y++){ 106 | // top 107 | t1=dyn[x % 2][y-1] + score(getFromS(x-1), getFromMap(y-2), false); 108 | // left 109 | t2=dyn[(x-1)%2][y] + score(getFromS(x-2), getFromMap(y-1), false); 110 | // diagonal 111 | t3=dyn[(x-1)%2][y-1] + score(getFromS(x-1), getFromMap(y-1), true); 112 | 113 | dyn[x%2][y] = Math.min(Math.min(t1, t2),t3); 114 | } 115 | 116 | int y_min = -1; 117 | double f_min = Double.POSITIVE_INFINITY; 118 | for ( int y_ = 1; y_ < dyn[0].length - 1; y_++){ 119 | if ( f_min > dyn[x%2][y_] ){ 120 | f_min = dyn[x%2][y_]; 121 | y_min = y_; 122 | } 123 | } 124 | 125 | // y_min + 1 : index i is step i + 1 ( array starting at 0) 126 | progress = (y_min + 1) * avg_steplength; 127 | 128 | // Update fields in NPConfig conf: 129 | // Find out which edge we are on and how far on that edge: 130 | double tempLen = edges.get(0).getLen(); 131 | int edgeIndex = 0; 132 | while(tempLen < progress && edgeIndex < edges.size() ){ 133 | edgeIndex++; 134 | tempLen += edges.get(edgeIndex).getLen(); 135 | } 136 | 137 | conf.npCurLen = progress; 138 | for(int i = 0; i < edgeIndex; i++){ 139 | conf.npCurLen-=edges.get(i).getLen(); 140 | } 141 | 142 | conf.npPointer = edgeIndex; 143 | conf.npLastMatchedStep = y_min; 144 | conf.npMatchedSteps++; 145 | conf.npUnmatchedSteps = conf.npMatchedSteps - y_min; 146 | } 147 | 148 | private double getFromMap(int i){ 149 | if ( i < 0 ) 150 | return 0.0; 151 | else 152 | return from_map[i]; 153 | } 154 | 155 | private double getFromS(int i){ 156 | if ( i < 0 ) 157 | return 0.0; 158 | else 159 | return s.get(i).doubleValue(); 160 | } 161 | private double score(double x, double y, boolean diagonal){ 162 | double ret = 2.0; // = penalty 163 | 164 | // Sanitize: 165 | double t = Math.abs(x-y); 166 | t = (t>180.0) ? 360.0 - t : t; 167 | 168 | // And score: 169 | if ( t < 45.0 ){ 170 | ret = 0.0; 171 | } else if ( t < 90.0 ){ 172 | ret = 1.0; 173 | } else if ( t < 120.0 ){ 174 | ret = 2.0; 175 | } else { 176 | ret = 10.0; 177 | } 178 | 179 | ret = !diagonal ? ret + 1.5 : ret; 180 | return ret; 181 | } 182 | 183 | public double getProgress(){ 184 | return progress; 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/core/Positioner_OnlineFirstFit.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.core; 2 | 3 | import java.util.LinkedList; 4 | 5 | import de.uvwxy.footpath.graph.GraphEdge; 6 | import de.uvwxy.footpath.gui.Navigator; 7 | 8 | /** 9 | * A class calculating the position concerning First Fit 10 | * 11 | * @author Paul Smith 12 | * 13 | */ 14 | public class Positioner_OnlineFirstFit extends Positioner{ 15 | private NPConfig conf; 16 | private LinkedList navPathEdges = null; 17 | private int totalStepsWalked = 0; 18 | 19 | // store each step, with its direction 20 | private LinkedList dirHistory = new LinkedList(); 21 | // If this value is passed, lookahead is started 22 | private int maxFallBackSteps = 4; 23 | 24 | private Navigator nav = null; 25 | public Positioner_OnlineFirstFit(Navigator nav, LinkedList navPathEdges, NPConfig conf){ 26 | this.navPathEdges = navPathEdges; 27 | this.conf = conf; 28 | this.nav = nav; 29 | } 30 | 31 | /** 32 | * This is called each time a step is detected, and thus information about 33 | * the users whereabouts need to be updated 34 | */ 35 | public void addStep(double compValue) { 36 | 37 | totalStepsWalked++; 38 | dirHistory.add(new Double(compValue)); 39 | 40 | if (conf.npPointer < navPathEdges.size()) { 41 | // we haven't reached a destination 42 | // successive matching, incoming values are/have been 43 | // roughly(in range of acceptanceWidth) correct, and not more than maxFallBackSteps have been unmatched 44 | if (isInRange(compValue, navPathEdges.get(conf.npPointer).getCompDir(), nav.getAcceptanceWidth()) 45 | && ( (conf.npUnmatchedSteps <= maxFallBackSteps)) // unmatched steps might be errors 46 | || (nav.getNavPathLenLeft()0){ 62 | // Calculate length from number of steps on stairs 63 | conf.npCurLen += navPathEdges.get(conf.npPointer).getLen()/navPathEdges.get(conf.npPointer).getSteps(); 64 | } else if(navPathEdges.get(conf.npPointer).getSteps()==-1){ 65 | // Naive length for steps on stairs if steps undefined 66 | conf.npCurLen += nav.getNaiveStairsWidth(); 67 | } else { 68 | // Error in data: Edge isStairs, but not undefined/defined number of steps 69 | conf.npCurLen += conf.npStepSize; 70 | } 71 | } else { 72 | conf.npCurLen += conf.npStepSize; 73 | } 74 | 75 | 76 | if (conf.npCurLen >= navPathEdges.get(conf.npPointer).getLen()) { 77 | // Edge length was exceeded so skip to next edge 78 | conf.npPointer++; 79 | // ;) 80 | // Log.i("FOOTPATH", "progress on path"); 81 | // Reset amount of walked length to remainder of step length 82 | conf.npCurLen = conf.npCurLen - navPathEdges.get(conf.npPointer - 1).getLen(); 83 | // Stop navigating if we have passed the last edge 84 | if (conf.npPointer >= navPathEdges.size()) { 85 | nav.setNavigating(false); 86 | } 87 | } 88 | } else { 89 | // Step did not match current assumed edge, try lookahead, if we 90 | // calculated with steps being larger in reality 91 | // If steps are smaller in reality than try to wait and resize the step size 92 | 93 | // Increase amount of unmatched steps 94 | conf.npUnmatchedSteps++; 95 | // Log.i("FOOTPATH", "Unmatched steps = " + conf.npUnmatchedSteps); 96 | 97 | // Do not do look ahead if the direction matches the last edge. 98 | // Wait for user to turn on to this edge 99 | if(conf.npPointer >= 1 && conf.npCurLen <= conf.npStepSize && isInRange(compValue, navPathEdges.get(conf.npPointer-1).getCompDir(), 100 | nav.getAcceptanceWidth())){ 101 | return; 102 | } 103 | 104 | // Enough steps unmatched to start lookahead 105 | if(conf.npUnmatchedSteps > maxFallBackSteps){ 106 | 107 | conf = findMatch(conf, true); 108 | // CALL FOR NEW POSITION FIRST FIND 109 | } 110 | 111 | }// -> else 112 | } // if 113 | } 114 | 115 | public void recalcPos(){ 116 | // RECALCULATE POSITION FROM WHOLE ROUTE DATA 117 | NPConfig x = new NPConfig(conf); 118 | // reset route to beginning 119 | x.npLastMatchedStep = -1; 120 | x.npPointer = 0; 121 | x.npCurLen = 0.0; 122 | x.npUnmatchedSteps = dirHistory.size() - 1; 123 | 124 | x = findMatch(x, false); 125 | x.npLastMatchedStep = dirHistory.size() - 1; 126 | x.npMatchedSteps = x.npLastMatchedStep; 127 | 128 | // Log.i("FOOTPAHT", "Recalculated route"); 129 | // Update position on path 130 | conf = x; 131 | } 132 | 133 | /** 134 | * Calculate the best/first matching position on path, from given configuration 135 | * @param npC the current configuration to base calculation on 136 | * @param first set to true if first match should be returned 137 | * @return new configuration for position 138 | */ 139 | private NPConfig findMatch(NPConfig npC, boolean first){ 140 | 141 | if(dirHistory == null){ 142 | return npC; 143 | } 144 | 145 | if(dirHistory.size() == 0){ 146 | return npC; 147 | } 148 | 149 | // Move backwards through walkedHistory, and look forwards to match it to an edge. 150 | // This works based on the assumption that, at some point, we have correct values again. 151 | // Accept only if we have found at least minMetresToMatch on a single edge 152 | 153 | double lastDir = dirHistory.get(dirHistory.size()-1); // last unmatched value 154 | 155 | // Log.i("FOOTPATH", "Searching for " + lastDir); 156 | // Log.i("FOOTPATH", "Current edge " + npC.npPointer); 157 | 158 | int maxBackLogCount = Integer.MIN_VALUE; 159 | int newPointer = npC.npPointer; 160 | double newCurLen = 0.0; 161 | int minCount = 4; // minimal 4 steps to match backwards 162 | // Go through all remaining edges and find first edge matching current direction 163 | for(int localPointer = npC.npPointer; localPointer < navPathEdges.size(); localPointer++){ 164 | 165 | // Log.i("FOOTPATH", "Found edge (" + localPointer + ") in direction " + navPathEdges.get(localPointer).getCompDir()); 166 | if(isInRange(navPathEdges.get(localPointer).getCompDir(),lastDir,nav.getAcceptanceWidth())){ 167 | // There is an edge matching a direction on path 168 | // Log.i("FOOTPATH", "Edge (" + localPointer + ") is in range"); 169 | UglyObject o = new UglyObject(); 170 | o.count = 0; 171 | int oldCount = o.count; 172 | o.historyPointer = dirHistory.size()-1; 173 | int backLogPointer = localPointer; 174 | double edgeDir = navPathEdges.get(backLogPointer).getCompDir(); 175 | double edgeLength = navPathEdges.get(backLogPointer).getLen(); 176 | // Log.i("FOOTPATH", "Summing up path length " + lastDir); 177 | // Log.i("FOOTPATH", "backlogPointer = " + backLogPointer + ">" + npC.npPointer + " npLastMatchedStep"); 178 | while(backLogPointer > npC.npPointer && findMatchingSteps(o, edgeDir, npC, edgeLength, npC.npStepSize)){ 179 | oldCount = o.count; 180 | // Log.i("FOOTPATH", "Found matching steps into edgeDir = " + edgeDir + ", oldCount = " 181 | // + oldCount + ", " + o.historyPointer); 182 | backLogPointer--; 183 | 184 | if(backLogPointer<0){ 185 | break; 186 | } 187 | 188 | edgeDir = navPathEdges.get(backLogPointer).getCompDir(); 189 | // remember last count. on last loop o.count is set to zero 190 | } 191 | // Log.i("FOOTPATH", "Found " + oldCount + " matching steps"); 192 | if(oldCount >= minCount && oldCount>maxBackLogCount){ 193 | maxBackLogCount = oldCount; 194 | newPointer = localPointer; 195 | newCurLen = amountInSameDirection(dirHistory.size()-1,navPathEdges.get(localPointer), npC); 196 | 197 | if(first){ 198 | break; 199 | } 200 | } 201 | } else { 202 | 203 | // Log.i("FOOTPATH", "Edge (" + localPointer + ") not in range"); 204 | } 205 | } 206 | 207 | if(maxBackLogCount != Integer.MIN_VALUE){ 208 | // Log.i("FOOTPATH", "Found new position with " + (double)maxBackLogCount/(double)(dirHistory.size()-1-npC.npLastMatchedStep) + " confidence"); 209 | 210 | if(newPointer == npC.npPointer){ 211 | // Jump along same edge 212 | npC.npCurLen += newCurLen; 213 | if(npC.npCurLen > navPathEdges.get(npC.npPointer).getLen()){ 214 | // Don not exceed edge! 215 | npC.npCurLen = navPathEdges.get(npC.npPointer).getLen(); 216 | } 217 | } else { 218 | // Jump along a different edge 219 | npC.npPointer = newPointer; 220 | npC.npCurLen = newCurLen; 221 | if(npC.npCurLen > navPathEdges.get(npC.npPointer).getLen()){ 222 | // Don not exceed edge! 223 | npC.npCurLen = navPathEdges.get(npC.npPointer).getLen(); 224 | } 225 | } 226 | 227 | npC.npUnmatchedSteps = 0; 228 | npC.npLastMatchedStep = dirHistory.size() - 1; 229 | } 230 | 231 | return npC; 232 | } 233 | 234 | private class UglyObject{ 235 | int count; 236 | int historyPointer; 237 | } 238 | 239 | private boolean findMatchingSteps(UglyObject o, double edgeDir, NPConfig npC, double edgeLength, double stepLength){ 240 | int oldCount = o.count; 241 | // Log.i("FOOTPATH", "findMatchingSteps: " + oldCount); 242 | while(o.historyPointer >= 0 243 | && o.historyPointer > npC.npLastMatchedStep 244 | && isInRange(dirHistory.get(o.historyPointer), edgeDir, nav.getAcceptanceWidth())){ 245 | o.count++; 246 | o.historyPointer--; 247 | 248 | double lengthToAdd = 0.0; 249 | if(navPathEdges.get(npC.npPointer).isStairs()){ 250 | // Don't use step length because of stairs 251 | if(navPathEdges.get(npC.npPointer).getSteps()>0){ 252 | // Calculate length from number of steps on stairs 253 | lengthToAdd = navPathEdges.get(npC.npPointer).getLen()/navPathEdges.get(npC.npPointer).getSteps(); 254 | } else if(navPathEdges.get(npC.npPointer).getSteps()==-1){ 255 | // Naive length for steps on stairs if steps undefined 256 | lengthToAdd= nav.getNaiveStairsWidth(); 257 | } else { 258 | // Error in data: Edge isStairs, but not undefined/defined number of steps 259 | lengthToAdd = npC.npStepSize; 260 | } 261 | } else { 262 | lengthToAdd = npC.npStepSize; 263 | } 264 | 265 | if(edgeLength <= (o.count-oldCount)*lengthToAdd){ 266 | // Log.i("FOOTPATH", "findMatchingSteps/ edgeLength (" + edgeLength + ") <= " 267 | // + (o.count-oldCount)*lengthToAdd); 268 | // return true if whole edge has been traveled along 269 | return true; 270 | } 271 | } 272 | // Log.i("FOOTPATH", "found: " + oldCount); 273 | if(oldCount != o.count){ 274 | return true; 275 | } else { 276 | return false; 277 | } 278 | } 279 | 280 | 281 | private double amountInSameDirection(int historyPointer, GraphEdge edge, NPConfig npC){ 282 | double retLength = 0.0; 283 | 284 | while(historyPointer >= 0 285 | && historyPointer >= npC.npLastMatchedStep 286 | && isInRange(edge.getCompDir(), dirHistory.get(historyPointer), nav.getAcceptanceWidth())){ 287 | // Log.i("FOOTPATH", "Adding amount into direction " + edge.getCompDir()); 288 | historyPointer--; 289 | double lengthToAdd = 0.0; 290 | if(edge.isStairs()){ 291 | // Don't use step length because of stairs 292 | if(edge.getSteps()>0){ 293 | // Calculate length from number of steps on stairs 294 | lengthToAdd = edge.getLen()/edge.getSteps(); 295 | } else if(edge.getSteps()==-1){ 296 | // Naive length for steps on stairs if steps undefined 297 | lengthToAdd= nav.getNaiveStairsWidth(); 298 | } else { 299 | // Error in data: Edge isStairs, but not undefined/defined number of steps 300 | lengthToAdd = npC.npStepSize; 301 | } 302 | } else { 303 | lengthToAdd = npC.npStepSize; 304 | } 305 | retLength += lengthToAdd; 306 | } 307 | return retLength; 308 | } 309 | 310 | @Override 311 | public double getProgress() { 312 | double len = 0.0; 313 | // sum all traversed edges 314 | for(int i = 0; i < conf.npPointer; i++){ 315 | len += navPathEdges.get(i).getLen(); 316 | } 317 | // and how far we have walked on current edge 318 | len += conf.npCurLen; 319 | return len; 320 | } 321 | 322 | 323 | } 324 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/core/StepDetection.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.core; 2 | 3 | import java.util.List; 4 | import java.util.Timer; 5 | import java.util.TimerTask; 6 | 7 | import android.content.Context; 8 | import android.hardware.Sensor; 9 | import android.hardware.SensorEvent; 10 | import android.hardware.SensorEventListener; 11 | import android.hardware.SensorManager; 12 | import android.util.Log; 13 | import de.uvwxy.footpath.ToolBox; 14 | 15 | /** 16 | * This class is fed with data from the Accelerometer and Compass sensors. If a step is detected on the acc 17 | * data it calls the trigger function on its interface StepTrigger, with the given direction. 18 | * 19 | * Usage: 20 | * Create an object: stepDetection = new StepDetection(this, this, a, peak, step_timeout_ms); 21 | * @author Paul Smith 22 | * 23 | */ 24 | public class StepDetection { 25 | public final long INTERVAL_MS = 1000/30; 26 | 27 | // Hold an interface to notify the outside world of detected steps 28 | private StepTrigger st; 29 | // Context needed to get access to sensor service 30 | private Context context; 31 | 32 | private static SensorManager sm; // Holds references to the SensorManager 33 | List lSensor; // List of all sensors 34 | 35 | // private double compassValue = -1.0; // Last compass value 36 | 37 | private static final int vhSize = 6; 38 | private double[] values_history = new double[vhSize]; 39 | private int vhPointer = 0; 40 | 41 | private double a; 42 | private double peak; 43 | private int step_timeout_ms; 44 | private long last_step_ts = 0; 45 | // private double old_z = 0.0; 46 | 47 | // last acc is low pass filtered 48 | private double[] lastAcc = new double[] {0.0, 0.0, 0.0}; 49 | // last comp is untouched 50 | private double[] lastComp = new double[] {0.0, 0.0, 0.0}; 51 | 52 | private int round = 0; 53 | 54 | /** 55 | * Handles sensor events. Updates the sensor 56 | */ 57 | public SensorEventListener mySensorEventListener = new SensorEventListener() { 58 | @Override 59 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 60 | // Auto-generated method stub 61 | } 62 | 63 | @Override 64 | public void onSensorChanged(SensorEvent event) { 65 | switch (event.sensor.getType()) { 66 | case Sensor.TYPE_ACCELEROMETER: 67 | st.dataHookAcc(System.currentTimeMillis(), event.values[0], event.values[1], event.values[2]); 68 | // just update the oldest z value 69 | lastAcc[0] = ToolBox.lowpassFilter(lastAcc[0], event.values[0], a); 70 | lastAcc[1] = ToolBox.lowpassFilter(lastAcc[1], event.values[1], a); 71 | lastAcc[2] = ToolBox.lowpassFilter(lastAcc[2], event.values[2], a); 72 | break; 73 | case Sensor.TYPE_ORIENTATION: 74 | st.dataHookComp(System.currentTimeMillis(), event.values[0], event.values[1], event.values[2]); 75 | lastComp[0] = event.values[0]; 76 | lastComp[1] = event.values[1]; 77 | lastComp[2] = event.values[2]; 78 | break; 79 | default: 80 | }// switch (event.sensor.getType()) 81 | } 82 | }; 83 | 84 | public double getA() { 85 | return a; 86 | } 87 | 88 | public double getPeak() { 89 | return peak; 90 | } 91 | 92 | public int getStep_timeout_ms() { 93 | return step_timeout_ms; 94 | } 95 | 96 | public void setA(double a) { 97 | this.a = a; 98 | } 99 | 100 | public void setPeak(double peak) { 101 | this.peak = peak; 102 | } 103 | 104 | public void setStep_timeout_ms(int stepTimeoutMs) { 105 | step_timeout_ms = stepTimeoutMs; 106 | } 107 | 108 | public StepDetection(Context context, StepTrigger st, double a, double peak, int step_timeout_ms){ 109 | this.context = context; 110 | this.st = st; 111 | this.a = a; 112 | this.peak = peak; 113 | this.step_timeout_ms = step_timeout_ms; 114 | } 115 | 116 | /** 117 | * Enable step detection 118 | */ 119 | public void load(){ 120 | // Sensors 121 | sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); 122 | lSensor = sm.getSensorList(Sensor.TYPE_ALL); 123 | 124 | for (int i = 0; i < lSensor.size(); i++) { 125 | // Register only compass and accelerometer 126 | if (lSensor.get(i).getType() == Sensor.TYPE_ACCELEROMETER 127 | || lSensor.get(i).getType() == Sensor.TYPE_ORIENTATION) { 128 | sm.registerListener(mySensorEventListener, lSensor.get(i), SensorManager.SENSOR_DELAY_FASTEST); 129 | } 130 | } 131 | 132 | // Register timer 133 | timer = new Timer("UpdateData", false); 134 | TimerTask task = new TimerTask(){ 135 | 136 | @Override 137 | public void run() { 138 | updateData(); 139 | } 140 | }; 141 | timer.schedule(task, 0, INTERVAL_MS); 142 | } 143 | 144 | /** 145 | * Disable step detection 146 | */ 147 | public void unload(){ 148 | timer.cancel(); 149 | timer.purge(); 150 | timer = null; 151 | sm.unregisterListener(mySensorEventListener); 152 | } 153 | 154 | /** 155 | * This is called every INTERVAL_MS ms from the TimerTask. 156 | */ 157 | private void updateData(){ 158 | // Get current time for time stamps 159 | long now_ms = System.currentTimeMillis(); 160 | 161 | // Create local value for compass and old_z, such that it is consistent during logs 162 | // (It might change in between, which is circumvented by this) 163 | 164 | // array.clone() does not work here!! 165 | // this does not work as well!! 166 | // double[] oldAcc = {lastAcc[0],lastAcc[1],lastAcc[2]}; 167 | // double[] oldComp = {lastComp[0],lastComp[1],lastComp[2]}; 168 | double[] oldAcc = new double[3]; 169 | System.arraycopy(lastAcc, 0, oldAcc, 0, 3); 170 | double[] oldComp = new double[3]; 171 | System.arraycopy(lastComp, 0, oldComp, 0, 3); 172 | double lCompass = oldComp[0]; 173 | double lOld_z = oldAcc[2]; 174 | st.timedDataHook(now_ms, oldAcc, oldComp); 175 | 176 | addData(lOld_z); 177 | 178 | // Check if a step is detected upon data 179 | if((now_ms - last_step_ts) > step_timeout_ms && checkForStep(peak)){ 180 | // Set latest detected step to "now" 181 | last_step_ts = now_ms; 182 | // Call algorithm for navigation/updating position 183 | st.trigger(now_ms, lCompass); 184 | Log.i("FOOTPATH", "Detected step in round = " + round + " @ "+ now_ms); 185 | 186 | } 187 | round++; 188 | } 189 | 190 | private void addData(double value){ 191 | values_history[vhPointer % vhSize] = value; 192 | vhPointer++; 193 | vhPointer = vhPointer % vhSize; 194 | } 195 | 196 | private boolean checkForStep(double peakSize) { 197 | // Add value to values_history 198 | 199 | int lookahead = 5; 200 | double diff = peakSize; 201 | 202 | 203 | for( int t = 1; t <= lookahead; t++){ 204 | if((values_history[(vhPointer - 1 - t + vhSize + vhSize) % vhSize] - 205 | values_history[(vhPointer - 1 + vhSize) % vhSize] 206 | > diff)){ 207 | Log.i("FOOTPATH", "Detected step with t = " + t + ", diff = " + diff + " < " + (values_history[(vhPointer - 1 - t + vhSize + vhSize) % vhSize] - 208 | values_history[(vhPointer - 1 + vhSize) % vhSize])); 209 | return true; 210 | } 211 | } 212 | return false; 213 | } 214 | 215 | Timer timer; 216 | } 217 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/core/StepTrigger.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.core; 2 | 3 | /** 4 | * An interface to be notified abuot detected steps and their directions. Also 5 | * there are hooks to to obtain values from sensors. 6 | * 7 | * @author Paul Smith 8 | * 9 | */ 10 | public interface StepTrigger { 11 | 12 | /** 13 | * Called each time a step is triggered. 14 | * 15 | * @param now_ms the time stamp of the detected step 16 | * @param compDir the compass bearing 17 | */ 18 | public void trigger(long now_ms, double compDir); 19 | 20 | /** 21 | * Called each time the accelerometer sensor values change 22 | * 23 | * @param now_ms the time stamp of the changed values 24 | * @param x x-axis 25 | * @param y y-axis 26 | * @param z z-axis 27 | */ 28 | public void dataHookAcc(long now_ms, double x, double y, double z); 29 | 30 | /** 31 | * Called each time the compass sensor values change 32 | * 33 | * @param now_ms the time stamp of the changed values 34 | * @param x x-axis 35 | * @param y y-axis 36 | * @param z z-axis 37 | */ 38 | public void dataHookComp(long now_ms, double x, double y, double z); 39 | 40 | /** 41 | * Called each time a sample is used to detect steps 42 | * 43 | * @param now_ms the time stamp of the sample 44 | * @param acc the accelerometer value (z-axis) 45 | * @param comp the compass bearing 46 | */ 47 | public void timedDataHook(long now_ms, double[] acc, double[] comp); 48 | } 49 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/graph/Graph.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.graph; 2 | 3 | import java.io.IOException; 4 | import java.util.LinkedList; 5 | import java.util.Stack; 6 | 7 | import org.xmlpull.v1.XmlPullParser; 8 | import org.xmlpull.v1.XmlPullParserException; 9 | 10 | 11 | import android.content.res.XmlResourceParser; 12 | 13 | /** 14 | * This class is used to create a graph from XML files stored in the directory 15 | * res/xml. Data from multiple files/layers can be joined into a single map/graph 16 | * with the function mergeNodes(). After graph creation use functions implemented 17 | * in this class to find routes, nodes, etc. 18 | * 19 | * @author Paul Smith 20 | * 21 | */ 22 | public class Graph { 23 | public LinkedList nodes; 24 | public LinkedList edges; 25 | 26 | private GraphNode[] array_nodes_by_id; 27 | private GraphNode[] array_nodes_by_name; 28 | 29 | public Graph(){ 30 | nodes = new LinkedList(); 31 | edges = new LinkedList(); 32 | } 33 | 34 | public boolean addToGraphFromXMLResourceParser(XmlResourceParser xrp) throws XmlPullParserException, IOException{ 35 | boolean ret = false; // return value 36 | if(xrp == null){ 37 | return ret; 38 | } 39 | 40 | boolean isOsmData = false; // flag to wait for osm data 41 | 42 | GraphNode tempNode = new GraphNode(); // temporary node to be added to all nodes in file 43 | GraphNode NULL_NODE = new GraphNode(); // 'NULL' node to point to, for dereferencing 44 | GraphWay tempWay = new GraphWay(); // temporary way to be added to all nodes in file 45 | GraphWay NULL_WAY = new GraphWay(); // 'NULL' node to point to, for dereferencing 46 | 47 | LinkedList allNodes = new LinkedList(); // store all nodes found in file 48 | LinkedList allWays = new LinkedList(); // store all ways found in file 49 | 50 | xrp.next(); 51 | int eventType = xrp.getEventType(); 52 | while (eventType != XmlPullParser.END_DOCUMENT) { 53 | switch(eventType){ 54 | case XmlPullParser.START_DOCUMENT: 55 | break; 56 | case XmlPullParser.START_TAG: 57 | if(!isOsmData){ 58 | if(xrp.getName().equals("osm")){ 59 | isOsmData = true; // osm 60 | // TODO: Test for correct version? (v0.6) 61 | } 62 | } else { 63 | int attributeCount = xrp.getAttributeCount(); 64 | if(xrp.getName().equals("node")){ // node 65 | tempNode = new GraphNode(); 66 | for(int i = 0; i < attributeCount; i++){ 67 | if(xrp.getAttributeName(i).equals("id")){ 68 | tempNode.setId(xrp.getAttributeIntValue(i, 0)); // node.id 69 | } if(xrp.getAttributeName(i).equals("lat")){ 70 | tempNode.setLat(Double.parseDouble(xrp.getAttributeValue(i))); // node.lat 71 | } if(xrp.getAttributeName(i).equals("lon")){ 72 | tempNode.setLon(Double.parseDouble(xrp.getAttributeValue(i))); // node.lon 73 | } 74 | } 75 | } else if(xrp.getName().equals("tag")){ // tag 76 | if(tempNode != NULL_NODE){ // node.tag 77 | for(int i = 0; i < attributeCount; i++){ 78 | if(xrp.getAttributeName(i).equals("k") 79 | && xrp.getAttributeValue(i).equals("indoor")){ // node.tag.indoor 80 | String v = xrp.getAttributeValue(i + 1); 81 | tempNode.setIndoors(v.equals("yes")); 82 | if(v.equals("door")){ 83 | tempNode.setIndoors(true); 84 | tempNode.setDoor(true); // this is a door (which is always inDOORS) ;) 85 | } 86 | } else if(xrp.getAttributeName(i).equals("k") 87 | && xrp.getAttributeValue(i).equals("name")){ // node.tag.name 88 | String v = xrp.getAttributeValue(i + 1); 89 | tempNode.setName(v); 90 | } else if(xrp.getAttributeName(i).equals("k") 91 | && xrp.getAttributeValue(i).equals("merge_id")){ // node.tag.merge_id 92 | String v = xrp.getAttributeValue(i + 1); 93 | tempNode.setMergeId(v); 94 | } else if(xrp.getAttributeName(i).equals("k") 95 | && xrp.getAttributeValue(i).equals("step_count")){ // node.tag.step_count 96 | int v = xrp.getAttributeIntValue(i + 1, Integer.MAX_VALUE); 97 | tempNode.setSteps(v); 98 | } else if(xrp.getAttributeName(i).equals("k") 99 | && xrp.getAttributeValue(i).equals("level")){ // node.tag.level 100 | String v = xrp.getAttributeValue(i + 1); 101 | float f = Float.parseFloat(v); 102 | tempNode.setLevel(f); 103 | } 104 | 105 | } 106 | } else { // way.tag 107 | for(int i = 0; i < attributeCount; i++){ 108 | if(xrp.getAttributeName(i).equals("k") 109 | && xrp.getAttributeValue(i).equals("wheelchair")){ // way.tag.wheelchair 110 | String v = xrp.getAttributeValue(i + 1); 111 | short wheelchair = (short) (v.equals("yes") ? 1 : v.equals("limited")?0: -1); 112 | tempWay.setWheelchair(wheelchair); 113 | } else if(xrp.getAttributeName(i).equals("k") 114 | && xrp.getAttributeValue(i).equals("step_count")){ // way.tag.step_count 115 | int v = xrp.getAttributeIntValue(i + 1, Integer.MAX_VALUE); 116 | tempWay.setSteps(v); 117 | } else if(xrp.getAttributeName(i).equals("k") 118 | && xrp.getAttributeValue(i).equals("level")){ // way.tag.level 119 | String v = xrp.getAttributeValue(i + 1); 120 | float f = Float.parseFloat(v); 121 | tempWay.setLevel(f); 122 | } else if(xrp.getAttributeName(i).equals("k") 123 | && xrp.getAttributeValue(i).equals("indoor")){ // way.tag.indoor 124 | String v = xrp.getAttributeValue(i + 1); 125 | tempWay.setIndoor(v.equals("yes")); 126 | }else if(xrp.getAttributeName(i).equals("k") 127 | && xrp.getAttributeValue(i).equals("highway")){ // way.tag.highway 128 | String v = xrp.getAttributeValue(i + 1); 129 | if(v.equals("steps")){ 130 | tempWay.setWheelchair((short)-1); 131 | if(tempWay.getSteps() == 0){ // no steps configured before 132 | tempWay.setSteps(-1); // so set to undefined (but present), 133 | // otherwise might be set later 134 | } 135 | } 136 | if(v.equals("elevator")){ 137 | tempWay.setWheelchair((short)1); 138 | tempWay.setSteps(-2); 139 | } 140 | } 141 | } 142 | } 143 | 144 | } else if(xrp.getName().equals("way")){ // way 145 | tempWay = new GraphWay(); 146 | for(int i = 0; i < attributeCount; i++){ 147 | if(xrp.getAttributeName(i).equals("id")){ 148 | tempWay.setId(xrp.getAttributeIntValue(i, 0)); // way.id 149 | } 150 | } 151 | } else if(xrp.getName().equals("nd")){ // way.nd 152 | for(int i = 0; i < attributeCount; i++){ 153 | if(xrp.getAttributeName(i).equals("ref")){ // way.nd.ref 154 | String v = xrp.getAttributeValue(i); 155 | int ref = Integer.parseInt(v); 156 | tempWay.addRef(ref); 157 | } 158 | } 159 | } 160 | } 161 | break; 162 | case XmlPullParser.END_TAG: 163 | if(isOsmData){ 164 | if(xrp.getName().equals("osm")){ 165 | ret = true; 166 | } else if(xrp.getName().equals("node")){ // node 167 | allNodes.add(tempNode); 168 | tempNode = NULL_NODE; 169 | } else if(xrp.getName().equals("tag")){ // tag 170 | 171 | } else if(xrp.getName().equals("way")){ // way 172 | allWays.add(tempWay); 173 | tempWay = NULL_WAY; 174 | } else if(xrp.getName().equals("nd")){ // way.nd 175 | 176 | } 177 | } 178 | break; 179 | default: 180 | } 181 | eventType = xrp.next(); 182 | } 183 | 184 | LinkedList remainingWays = new LinkedList(); 185 | 186 | for(GraphWay way : allWays){ // find ways which are indoors at some point 187 | LinkedList refs = way.getRefs(); 188 | if(way.isIndoor()){ // whole path is indoors -> keep 189 | remainingWays.add(way); 190 | } else { // check for path with indoor node 191 | boolean stop = false; 192 | for(Integer ref : refs){ // check if there is a node on path which is indoors 193 | for(GraphNode node : allNodes){ 194 | if(node.getId() == ref.intValue()){ 195 | remainingWays.add(way); 196 | stop = true; // found indoor node on path to be added to graph 197 | // thus stop both for loops and continue with next way 198 | } 199 | if(stop) 200 | break; 201 | } 202 | if(stop) 203 | break; 204 | } 205 | } 206 | } 207 | 208 | if(remainingWays.size() == 0) // return false, nothing to be added to graph 209 | return false; 210 | 211 | for(GraphWay way : remainingWays){ 212 | short wheelchair = way.getWheelchair(); 213 | float level = way.getLevel(); 214 | boolean indoor = way.isIndoor(); 215 | GraphNode firstNode = getNode(allNodes,way.getRefs().get(0).intValue()); 216 | for(int i = 1; i <= way.getRefs().size() - 1; i++){ 217 | GraphNode nextNode = getNode(allNodes,way.getRefs().get(i).intValue()); 218 | double len = getDistance(firstNode.getLat(), // get length between P1 and P2 219 | firstNode.getLon(), 220 | nextNode.getLat(), 221 | nextNode.getLon()); 222 | double compDegree = getInitialBearing(firstNode.getLat(), // get initial bearing between P1 and P2 223 | firstNode.getLon(), 224 | nextNode.getLat(), 225 | nextNode.getLon()); 226 | GraphEdge tempEdge = new GraphEdge(firstNode, nextNode, len, compDegree, wheelchair, level,indoor); 227 | if(way.getSteps()>0){ // make edge a staircase if steps_count 228 | tempEdge.setStairs(true); // was set correctly 229 | tempEdge.setElevator(false); 230 | tempEdge.setSteps(way.getSteps()); 231 | } else if(way.getSteps()==-1){ 232 | tempEdge.setStairs(true); // make edge a staircase if steps_count 233 | tempEdge.setElevator(false); 234 | tempEdge.setSteps(-1); // was set to -1 (undefined steps) 235 | } else if(way.getSteps()==-2){ 236 | tempEdge.setStairs(false); // make edge an elevator if steps_count 237 | tempEdge.setElevator(true); 238 | tempEdge.setSteps(-2); // was set to -2 239 | } else if(way.getSteps() == 0){ 240 | tempEdge.setStairs(false); 241 | tempEdge.setElevator(false); 242 | tempEdge.setSteps(0); 243 | } 244 | edges.add(tempEdge); // add edge to graph 245 | if(!nodes.contains(firstNode)){ 246 | nodes.add(firstNode); // add node to graph if not present 247 | } 248 | firstNode = nextNode; 249 | } 250 | 251 | if(!nodes.contains(firstNode)){ 252 | nodes.add(firstNode); // add last node to graph if not present 253 | } 254 | } 255 | 256 | return ret; 257 | } 258 | 259 | // use this to add edges for stairs to flags, this should be called once 260 | public void mergeNodes(){ 261 | // Edges are note inserted anymore. 262 | // Nodes are "Merged". Currently. 263 | LinkedList nodesWithMergeId = new LinkedList(); 264 | // Collect all relevant nodes to merge 265 | for(GraphNode node: nodes){ 266 | if(node.getMergeId()!=null){ 267 | nodesWithMergeId.add(node); 268 | } 269 | } 270 | for(GraphNode node: nodesWithMergeId){ 271 | for(GraphNode otherNode: nodesWithMergeId){ 272 | // Only merge if same id, but not same node! 273 | if(node.getMergeId() != null && node.getMergeId().equals(otherNode.getMergeId()) 274 | && !node.equals(otherNode)){ 275 | // Update all references pointing to otherNode to node 276 | for(GraphEdge edge: edges){ 277 | if(edge.getNode0().equals(otherNode)){ 278 | edge.setNode0(node); 279 | } 280 | if(edge.getNode1().equals(otherNode)){ 281 | edge.setNode1(node); 282 | } 283 | } 284 | // otherNode was merged/removed, do not check 285 | otherNode.setMergeId(null); 286 | } 287 | } 288 | } 289 | // Create arrays for binary search 290 | array_nodes_by_id = sortNodesById(nodes); 291 | array_nodes_by_name = sortNodesByName(nodes); 292 | // Add edges to node, faster look up for neighbors 293 | for(GraphEdge edge: edges){ 294 | GraphNode n0 = edge.getNode0(); 295 | GraphNode n1 = edge.getNode1(); 296 | if(!n0.getLocEdges().contains(edge)){ 297 | n0.getLocEdges().add(edge); 298 | } 299 | if(!n1.getLocEdges().contains(edge)){ 300 | n1.getLocEdges().add(edge); 301 | } 302 | } 303 | } 304 | 305 | 306 | public Stack getShortestPath(String from, String to, 307 | boolean staircase, boolean elevator, boolean outside){ 308 | GraphNode gnFrom = getNodeFromName(from); 309 | GraphNode gnTo = getNodeFromName(to); 310 | return getShortestPath(gnFrom, gnTo, staircase, elevator, outside); 311 | } 312 | public Stack getShortestPath(int from, String to, 313 | boolean staircase, boolean elevator, boolean outside){ 314 | GraphNode gnFrom = getNode(from); 315 | GraphNode gnTo = getNodeFromName(to); 316 | return getShortestPath(gnFrom, gnTo, staircase, elevator, outside); 317 | } 318 | 319 | // Returns a stack of nodes, with the destination at the bottom using 320 | // Dykstra's algorithm 321 | public Stack getShortestPath(GraphNode from, GraphNode to, 322 | boolean staircase, boolean elevator, boolean outside){ 323 | 324 | if(from == null || to == null){ 325 | return null; 326 | } 327 | 328 | int remaining_nodes = array_nodes_by_id.length; 329 | GraphNode[] previous = new GraphNode[array_nodes_by_id.length]; 330 | double[] dist = new double[array_nodes_by_id.length]; 331 | boolean[] visited = new boolean[array_nodes_by_id.length]; 332 | 333 | // Set initial values 334 | for(int i = 0; i < array_nodes_by_id.length; i++){ 335 | dist[i] = Double.POSITIVE_INFINITY; 336 | previous[i] = null; 337 | visited[i] = false; 338 | } 339 | dist[getNodePosInIdArray(from)] = 0; 340 | while(remaining_nodes>0){ 341 | // Vertex u in q with smallest dist[] 342 | GraphNode u; 343 | double minDist = Double.POSITIVE_INFINITY; 344 | int u_i = -1; 345 | for(int i = 0; i < array_nodes_by_id.length; i++){ 346 | if(!visited[i] && dist[i] nOuIq = getNeighbours(visited, u, staircase, elevator, outside); 365 | if(u.equals(to)){ 366 | // u = to -> found path to destination 367 | // Build stack of nodes, destination at the botton 368 | Stack s = new Stack(); 369 | while(previous[u_i]!=null){ 370 | s.push(u); 371 | u_i = getNodePosInIdArray(u); 372 | u = previous[u_i]; 373 | } 374 | return s; 375 | }else { 376 | remaining_nodes--; 377 | } 378 | for(GraphNode v : nOuIq){ 379 | double dist_alt = dist[u_i] + dist(u,v); 380 | int v_i = getNodePosInIdArray(v); 381 | if(dist_alt < dist[v_i]){ 382 | dist[v_i] = dist_alt; 383 | previous[v_i] = u; 384 | } 385 | } 386 | } 387 | return null; 388 | } 389 | 390 | // Returns the distance of two nodes by finding the corresponding edge and reading the len value 391 | private double dist(GraphNode from, GraphNode to){ 392 | double ret = Double.POSITIVE_INFINITY; 393 | ret = getEdge(from,to).getLen(); 394 | return ret; 395 | } 396 | 397 | // Returns all neighbors of given node from a given subset (list) of nodes in this graph 398 | private LinkedList getNeighbours(boolean[] visited, GraphNode node, boolean staircase, boolean elevator, boolean outside){ 399 | 400 | LinkedList ret = new LinkedList(); 401 | for(GraphEdge edge : node.getLocEdges()){ // check all edges if they contain node 402 | if(edge.isStairs() && !staircase ){ // edge has steps, but not allowed -> skip 403 | continue; 404 | } 405 | if(edge.isElevator() && !elevator ){ // edge is elevator, but not allowed -> skip 406 | continue; 407 | } 408 | if(!edge.isIndoor() && !outside){ // edge is outdoors, but not allowed -> skip 409 | continue; 410 | } 411 | 412 | GraphNode buf = null; 413 | if(edge.getNode0().equals(node)){ // node0 is node 414 | buf = edge.getNode1(); // add node1 415 | } else if(edge.getNode1().equals(node)){ // node 1 is node 416 | buf = edge.getNode0(); // add node0 417 | } 418 | if(outside){ 419 | if(buf!=null){ // if outside, all nodes are allowed 420 | if(!ret.contains(buf) && !visited[getNodePosInIdArray(buf)]){ 421 | ret.add(buf); // add buf only once, iff not visited 422 | } 423 | } 424 | } else { // if !outside, only indoor nodes are allowed 425 | if(buf!=null && buf.isIndoors()){ 426 | if(!ret.contains(buf) && !visited[getNodePosInIdArray(buf)]){ 427 | ret.add(buf); // add buf only once, iff not visited 428 | } 429 | } 430 | } 431 | } 432 | return ret; 433 | } 434 | 435 | // return node pos via binary search 436 | private int getNodePosInIdArray(GraphNode node){ 437 | int u = 0; 438 | int o = array_nodes_by_id.length-1; 439 | int m = 0; 440 | 441 | while(!(o < u)) { 442 | m = (u+o)/2; 443 | if(node.getId() == array_nodes_by_id[m].getId()){ 444 | return m; 445 | } 446 | if(node.getId() < array_nodes_by_id[m].getId()){ 447 | o = m-1; 448 | } else { 449 | u = m+1; 450 | } 451 | } 452 | return -1; 453 | } 454 | 455 | // This is the faster version which can be used after parsing the data 456 | public GraphNode getNode(int id){ 457 | int u = 0; 458 | int o = array_nodes_by_id.length-1; 459 | int m = 0; 460 | 461 | while(!(o < u)) { 462 | m = (u+o)/2; 463 | if(id == array_nodes_by_id[m].getId()){ 464 | return array_nodes_by_id[m]; 465 | } 466 | if(id < array_nodes_by_id[m].getId()){ 467 | o = m-1; 468 | } else { 469 | u = m+1; 470 | } 471 | } 472 | return null; 473 | } 474 | 475 | // This is the slower version which is used during parsing 476 | private GraphNode getNode(LinkedList list, int id){ 477 | for(GraphNode node: list){ 478 | if(node.getId() == id) 479 | return node; 480 | } 481 | return null; 482 | } 483 | 484 | // return all names of nodes != null in a String array 485 | public String[] getRoomList(){ 486 | String[] retArray = new String[array_nodes_by_name.length]; 487 | for(int i = 0; i < retArray.length; i++){ 488 | retArray[i] = array_nodes_by_name[i].getName(); 489 | } 490 | return retArray; 491 | } 492 | 493 | // creates a linked list form a stack, top to bottom 494 | public LinkedList getPathEdges(Stack navPath) { 495 | LinkedList pathEdges = new LinkedList(); 496 | GraphNode a = navPath.pop(); 497 | while(!navPath.isEmpty()){ 498 | GraphNode b = navPath.pop(); 499 | GraphEdge e = this.getEdge(a,b); 500 | if(e!=null){ 501 | pathEdges.add(e); 502 | } else { 503 | return null; 504 | } 505 | a = b; 506 | } 507 | return pathEdges; 508 | } 509 | 510 | // returns the edge containing nodes a and b 511 | public GraphEdge getEdge(GraphNode a, GraphNode b) { 512 | GraphEdge ret = null; 513 | for(GraphEdge edge : a.getLocEdges()){ // return edge if 514 | if(edge.getNode0().equals(a) && edge.getNode1().equals(b)){ // node0=a, node1=b 515 | ret = edge; // or 516 | }else if(edge.getNode1().equals(a) && edge.getNode0().equals(b)){ // node0=b, node1=a 517 | ret = edge; 518 | } // else null 519 | } 520 | return ret; 521 | } 522 | 523 | /** 524 | * Returns the distance between two nodes 525 | * @param node_0 first node 526 | * @param node_1 second node 527 | * @return the distance in meters 528 | */ 529 | public double getDistance(LatLonPos pos_0, GraphNode node_1){ 530 | return getDistance(pos_0.getLat(), pos_0.getLon(), node_1.getLat(), node_1.getLon()); 531 | } 532 | 533 | /** 534 | * Returns the distance between two points given in latitude/longitude 535 | * @param lat_1 latitude of first point 536 | * @param lon_1 longitude of first point 537 | * @param lat_2 latitude of second point 538 | * @param lon_2 longitude of second point 539 | * @return the distance in meters 540 | */ 541 | public double getDistance(double lat_1, double lon_1, double lat_2, double lon_2) { 542 | // source: http://www.movable-type.co.uk/scripts/latlong.html 543 | double dLon = lon_2 - lon_1; 544 | double dLat = lat_2 - lat_1; 545 | lat_1 = Math.toRadians(lat_1); 546 | lon_1 = Math.toRadians(lon_1); 547 | lat_2 = Math.toRadians(lat_2); 548 | lon_2 = Math.toRadians(lon_2); 549 | dLon = Math.toRadians(dLon); 550 | dLat = Math.toRadians(dLat); 551 | 552 | double r = 6378137; // km 553 | double a = Math.sin(dLat/2)*Math.sin(dLat/2) + 554 | Math.cos(lat_1)*Math.cos(lat_2) * 555 | Math.sin(dLon/2)*Math.sin(dLon/2); 556 | double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 557 | return c*r; 558 | } 559 | 560 | public double getInitialBearing(double lat_1, double lon_1, double lat_2, double lon_2) { 561 | // source: http://www.movable-type.co.uk/scripts/latlong.html 562 | double dLon = lon_2 - lon_1; 563 | lat_1 = Math.toRadians(lat_1); 564 | lon_1 = Math.toRadians(lon_1); 565 | lat_2 = Math.toRadians(lat_2); 566 | lon_2 = Math.toRadians(lon_2); 567 | dLon = Math.toRadians(dLon); 568 | double y = Math.sin(dLon) * Math.cos(lat_2); 569 | double x = Math.cos(lat_1) * Math.sin(lat_2) - 570 | Math.sin(lat_1) * Math.cos(lat_2) * Math.cos(dLon); 571 | double b = Math.atan2(y, x); 572 | b = Math.toDegrees(b); 573 | return (b < 0) ? b + 360.0 : b; 574 | } 575 | 576 | // returns the node with the given name, binary search 577 | public GraphNode getNodeFromName(String name){ 578 | int u = 0; 579 | int o = array_nodes_by_name.length-1; 580 | int m = 0; 581 | 582 | while(!(o < u)) { 583 | m = (u+o)/2; 584 | if(name.equals(array_nodes_by_name[m].getName())){ 585 | return array_nodes_by_name[m]; 586 | } 587 | if(name.compareTo(array_nodes_by_name[m].getName()) < 0){ 588 | o = m-1; 589 | } else { 590 | u = m+1; 591 | } 592 | } 593 | 594 | return null; 595 | } 596 | 597 | /** 598 | * Returns the closest node to a position at the given level 599 | * @param pos the position 600 | * @param level the level 601 | * @param indoor set to true if indoor nodes should be included 602 | * @param maxMeters limit of distance to a node 603 | * @return the closest GraphNode 604 | */ 605 | public GraphNode getClosestNodeToLatLonPos(LatLonPos pos, float level, boolean indoor, int maxMeters){ 606 | double minDistance = Double.MAX_VALUE; 607 | double tempDistance = Double.MAX_VALUE; 608 | GraphNode minDistNode = null; 609 | 610 | for(GraphNode node: nodes){ 611 | // First: node has to be at the same level 612 | // Second: if indoor = true, then take all nodes 613 | // Third: if indoor = false check if node is not indoors! 614 | if(node.getLevel() == level && (indoor || (node.isIndoors()==indoor))){ 615 | tempDistance = getDistance(pos, node); 616 | if(tempDistance < minDistance){ 617 | minDistance = tempDistance; 618 | minDistNode = node; 619 | } 620 | } 621 | } 622 | if(minDistance < maxMeters){ 623 | return minDistNode; 624 | } else { 625 | return null; 626 | } 627 | } 628 | 629 | public double getClosestDistanceToNode(LatLonPos pos, float level, boolean indoor){ 630 | double minDistance = Double.MAX_VALUE; 631 | double tempDistance = Double.MAX_VALUE; 632 | 633 | for(GraphNode node: nodes){ 634 | // First: node has to be at the same level 635 | // Second: if indoor = true, then take all nodes 636 | // Third: if indoor = false check if node is not indoors! 637 | if(node.getLevel() == level && (indoor || (node.isIndoors()!=indoor))){ 638 | tempDistance = getDistance(pos, node); 639 | if(tempDistance < minDistance){ 640 | minDistance = tempDistance; 641 | } 642 | } 643 | } 644 | return minDistance; 645 | } 646 | 647 | // creates an array, containing only nodes _with_ a name, sorted ascending 648 | private GraphNode[] sortNodesByName(LinkedList nodes){ 649 | GraphNode[] node_array; 650 | GraphNode temp = null; 651 | int num_nulls = 0; 652 | int c = 0; 653 | boolean not_sorted = true; 654 | // count number of nodes without a name (null) 655 | for(int i = 0; i < nodes.size(); i++){ 656 | if(nodes.get(i) != null && nodes.get(i).getName() == null){ 657 | num_nulls++; 658 | } 659 | } 660 | // create an array for nodes with a name 661 | node_array = new GraphNode[nodes.size() - num_nulls]; 662 | for(GraphNode node: nodes){ 663 | if(node != null && node.getName() != null){ 664 | // insert node with name into array 665 | node_array[c] = node; 666 | c++; 667 | } 668 | } 669 | // sort by name (bubble sort) 670 | while(not_sorted){ 671 | not_sorted = false; 672 | for(int i = 0; i < node_array.length - 1; i++){ 673 | if(node_array[i].getName().compareTo(node_array[i+1].getName()) > 0){ 674 | temp = node_array[i]; 675 | node_array[i] = node_array[i+1]; 676 | node_array[i+1] = temp; 677 | not_sorted = true; 678 | } 679 | } 680 | } 681 | // return array which had no nulls 682 | return node_array; 683 | } 684 | 685 | // creates an array,sorted by id ascending 686 | private GraphNode[] sortNodesById(LinkedList nodes){ 687 | GraphNode[] node_array; 688 | GraphNode temp = null; 689 | int c = 0; 690 | boolean not_sorted = true; 691 | // create an array for all nodes 692 | node_array = new GraphNode[nodes.size()]; 693 | for(GraphNode node: nodes){ 694 | if(node != null){ 695 | // insert node 696 | node_array[c] = node; 697 | c++; 698 | } 699 | } 700 | // sort by id (bubble sort) 701 | while(not_sorted){ 702 | not_sorted = false; 703 | for(int i = 0; i < node_array.length - 1; i++){ 704 | if(node_array[i].getId() > node_array[i+1].getId()){ 705 | temp = node_array[i]; 706 | node_array[i] = node_array[i+1]; 707 | node_array[i+1] = temp; 708 | not_sorted = true; 709 | } 710 | } 711 | } 712 | return node_array; 713 | } 714 | } 715 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/graph/GraphEdge.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.graph; 2 | 3 | /** 4 | * A class to maintain an edge in the graph. 5 | * 6 | * @author Paul Smith 7 | * 8 | */ 9 | public class GraphEdge { 10 | private GraphNode node0; 11 | private GraphNode node1; 12 | private double len; 13 | private double bearing; 14 | private short wheelchair; 15 | private boolean isStairs = false; 16 | private boolean isElevator = false; 17 | 18 | // >0 := number correct steps given 19 | // 0 := no steps 20 | // -1 := undefined number of steps 21 | // -2 := elevator 22 | private int numSteps = 0; 23 | 24 | private float level; 25 | private boolean isIndoor; 26 | 27 | /** 28 | * Constructor to create an empty edge with everything set to 0/null/false 29 | */ 30 | public GraphEdge() { 31 | this.node0 = null; 32 | this.node1 = null; 33 | this.len = 0.0; 34 | this.wheelchair = 1; 35 | this.level = Float.MAX_VALUE; 36 | this.isIndoor = false; 37 | } 38 | 39 | /** 40 | * Constructor to create an edge with given parameters. 41 | * 42 | * @param node0 the first GraphNode 43 | * @param node1 the second GraphNode 44 | * @param len the length of this edge 45 | * @param compDir the direction of this edge (node0 -> node1) 46 | * @param wheelchair the value concerning the wheelchair attribute 47 | * @param level the level of this edge 48 | * @param isIndoor true if is indoor 49 | */ 50 | public GraphEdge(GraphNode node0, GraphNode node1, double len, double compDir, short wheelchair, float level, boolean isIndoor) { 51 | this.node0 = node0; 52 | this.node1 = node1; 53 | this.len = len; 54 | this.bearing = compDir; 55 | this.wheelchair = wheelchair; 56 | this.level = level; 57 | this.isIndoor = isIndoor; 58 | } 59 | 60 | public double getCompDir() { 61 | return bearing; 62 | } 63 | 64 | public GraphNode getNode0() { 65 | return node0; 66 | } 67 | 68 | public GraphNode getNode1() { 69 | return node1; 70 | } 71 | 72 | public double getLen() { 73 | return len; 74 | } 75 | 76 | public short getWheelchair() { 77 | return wheelchair; 78 | } 79 | 80 | public boolean isStairs(){ 81 | return isStairs; 82 | } 83 | 84 | public boolean isElevator(){ 85 | return isElevator; 86 | } 87 | 88 | public int getSteps(){ 89 | return numSteps; 90 | } 91 | 92 | public float getLevel() { 93 | return level; 94 | } 95 | 96 | public boolean isIndoor(){ 97 | return isIndoor; 98 | } 99 | 100 | public void setCompDir(double compDir) { 101 | this.bearing = compDir; 102 | } 103 | 104 | public void setNode0(GraphNode node0) { 105 | this.node0 = node0; 106 | } 107 | 108 | public void setNode1(GraphNode node1) { 109 | this.node1 = node1; 110 | } 111 | 112 | public void setLen(double len) { 113 | this.len = len; 114 | } 115 | 116 | public void setWheelchair(short wheelchair) { 117 | this.wheelchair = wheelchair; 118 | } 119 | 120 | public void setStairs(boolean isStairs) { 121 | this.isStairs = isStairs; 122 | } 123 | 124 | public void setElevator(boolean isElevator) { 125 | this.isElevator = isElevator; 126 | } 127 | 128 | public void setSteps(int numSteps){ 129 | this.numSteps = numSteps; 130 | if(numSteps>0) 131 | this.setWheelchair((short)-1);//if steps, NO wheelchair 132 | } 133 | 134 | public void setLevel(float level) { 135 | this.level = level; 136 | } 137 | 138 | public void setLevel(boolean isIndoor){ 139 | this.isIndoor = isIndoor; 140 | } 141 | 142 | public boolean equals(GraphEdge edge){ 143 | if(edge == null) 144 | return false; 145 | return this.node0.equals(edge.getNode0()) && this.node1.equals(edge.getNode1()) 146 | || this.node0.equals(edge.getNode1()) && this.node1.equals(edge.getNode0()); 147 | } 148 | 149 | public boolean contains(GraphNode node){ 150 | return getNode0().equals(node) || getNode1().equals(node); 151 | } 152 | 153 | public String toString(){ 154 | String ret = "\nEdge(" + this.node0.getId() + " to " + this.node1.getId() + "): "; 155 | ret += "\n Length: " + this.len; 156 | ret += "\n Bearing: " + this.bearing; 157 | if(isStairs()){ 158 | ret += "\n Staircase with: " + this.getSteps() + " steps"; 159 | } 160 | if(isElevator()){ 161 | ret += "\n Elevator: yes"; 162 | } 163 | ret+="\n Level: " + level; 164 | return ret; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/graph/GraphElement.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.graph; 2 | 3 | /** 4 | * 5 | * @author Paul Smith 6 | * 7 | */ 8 | public class GraphElement { 9 | // nothing, just to have a parent for GraphNode and GraphEdge 10 | // TODO: delete this? 11 | } 12 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/graph/GraphNode.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.graph; 2 | 3 | import java.util.LinkedList; 4 | 5 | 6 | /** 7 | * A class to maintain a node with given parameters found in OSM/XML data. 8 | * 9 | * @author Paul Smith 10 | * 11 | */ 12 | public class GraphNode { 13 | private double lon; 14 | private double lat; 15 | private String name; 16 | private boolean isInDoors; 17 | private boolean isDoor; 18 | private float level; 19 | private int id; 20 | private String merge_id; 21 | private int numSteps = 0; 22 | private LinkedList loc_edges; 23 | 24 | /** 25 | * Constructor to create an empty node with everything set to 0/null/false 26 | */ 27 | public GraphNode() { 28 | this.lon = 0.0; 29 | this.lat = 0.0; 30 | this.name = null; 31 | this.isInDoors = false; 32 | this.isDoor = false; 33 | this.level = 0; 34 | this.merge_id = null; 35 | loc_edges = new LinkedList(); 36 | } 37 | 38 | /** 39 | * Constructor to create a node with given parameters. 40 | * 41 | * @param lon the longitude 42 | * @param lat the latitude 43 | * @param name the name 44 | * @param indoors true if is indoor node 45 | * @param isDoor true if is door 46 | * @param level the level 47 | */ 48 | public GraphNode(double lon, double lat, String name, boolean indoors, boolean isDoor, float level) { 49 | this.lon = lon; 50 | this.lat = lat; 51 | this.name = name; 52 | this.isInDoors = indoors; 53 | this.isDoor = isDoor; 54 | this.level = level; 55 | this.merge_id = null; 56 | loc_edges = new LinkedList(); 57 | } 58 | 59 | /** 60 | * Creates a LatLonPos object to return the location. 61 | * 62 | * @return the new LatLonPos object of this node's location 63 | */ 64 | public LatLonPos getPos(){ 65 | LatLonPos ret = new LatLonPos(); 66 | ret.setLat(lat); 67 | ret.setLon(lon); 68 | ret.setLevel(level); 69 | return ret; 70 | } 71 | 72 | public double getLon() { 73 | return lon; 74 | } 75 | 76 | public double getLat() { 77 | return lat; 78 | } 79 | 80 | public String getName() { 81 | return name; 82 | } 83 | 84 | public boolean isIndoors() { 85 | return isInDoors; 86 | } 87 | 88 | public boolean isDoor() { 89 | return isDoor; 90 | } 91 | 92 | public float getLevel() { 93 | return level; 94 | } 95 | 96 | public int getId() { 97 | return id; 98 | } 99 | 100 | public String getMergeId() { 101 | return this.merge_id; 102 | } 103 | 104 | public int getSteps(){ 105 | return numSteps; 106 | } 107 | 108 | public LinkedList getLocEdges(){ 109 | return loc_edges; 110 | } 111 | 112 | public void setLon(double lon) { 113 | this.lon = lon; 114 | } 115 | 116 | public void setLat(double lat) { 117 | this.lat = lat; 118 | } 119 | 120 | public void setName(String name) { 121 | this.name = name; 122 | } 123 | 124 | public void setIndoors(boolean indoors) { 125 | this.isInDoors = indoors; 126 | } 127 | 128 | public void setDoor(boolean door){ 129 | this.isDoor = door; 130 | } 131 | 132 | public void setLevel(float level) { 133 | this.level = level; 134 | } 135 | 136 | public void setId(int id){ 137 | this.id = id; 138 | } 139 | 140 | public void setMergeId(String mergeId) { 141 | this.merge_id = mergeId; 142 | } 143 | 144 | public void setSteps(int numSteps){ 145 | this.numSteps = numSteps; 146 | } 147 | 148 | public boolean equals(GraphNode node){ 149 | if(node == null) 150 | return false; 151 | return this.getId()==node.getId(); 152 | } 153 | 154 | public String toString(){ 155 | String ret = "\nNode(" + this.id +"): "; 156 | ret += name!=null?name:"N/A"; 157 | ret += isInDoors ? " (indoors)" : " (outdoors)"; 158 | ret += "\n Level: " + this.level; 159 | ret += "\n Lat: " + this.lat; 160 | ret += "\n Lon: " + this.lon; 161 | if(getMergeId()!=null){ 162 | ret +="\n Merges with: " + getMergeId(); 163 | } 164 | return ret; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/graph/GraphWay.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.graph; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * A class to maintain a way with given parameters found in OSM/XML data. 7 | * 8 | * @author Paul Smith 9 | * 10 | */ 11 | public class GraphWay { 12 | // all nodes on this path ( ref0 -> ref1 -> ref2 -> ...) 13 | private LinkedList refs; 14 | private int id; 15 | private short wheelchair; 16 | 17 | // >0 := number correct steps given 18 | // 0 := no steps 19 | // -1 := undefined number of steps 20 | // -2 := elevator 21 | private int numSteps = 0; 22 | 23 | // Float.MAX_VALUE == undefined! 24 | private float level; 25 | private boolean isIndoor; 26 | 27 | /** 28 | * Constructor to create an empty way. 29 | */ 30 | public GraphWay() { 31 | this.refs = new LinkedList(); 32 | this.id = 0; 33 | this.wheelchair = 1; 34 | this.level = Float.MAX_VALUE; 35 | } 36 | 37 | /** 38 | * Constructor to create a coordinate with given parameters. 39 | * 40 | * @param refs a LinkedList of Integers, references to GraphNodes 41 | * @param id the id of this way 42 | * @param wheelchair the value concerning the wheelchair attribute 43 | * @param level the level of this way 44 | */ 45 | public GraphWay(LinkedList refs, int id, short wheelchair, float level) { 46 | this.refs = refs; 47 | this.id = id; 48 | this.wheelchair = wheelchair; 49 | this.level = level; 50 | } 51 | 52 | public LinkedList getRefs() { 53 | return refs; 54 | } 55 | 56 | public int getId() { 57 | return id; 58 | } 59 | 60 | public short getWheelchair() { 61 | return wheelchair; 62 | } 63 | 64 | public int getSteps(){ 65 | return numSteps; 66 | } 67 | 68 | public float getLevel(){ 69 | return level; 70 | } 71 | 72 | public boolean isIndoor(){ 73 | return isIndoor; 74 | } 75 | 76 | public void setRefs(LinkedList refs) { 77 | this.refs = refs; 78 | } 79 | 80 | public void setId(int id) { 81 | this.id = id; 82 | } 83 | 84 | public void setWheelchair(short wheelchair) { 85 | this.wheelchair = wheelchair; 86 | } 87 | 88 | public void setSteps(int numSteps){ 89 | this.numSteps = numSteps; 90 | } 91 | 92 | public void setLevel(float level){ 93 | this.level = level; 94 | } 95 | 96 | public void setIndoor(boolean isIndoor){ 97 | this.isIndoor = isIndoor; 98 | } 99 | 100 | public void addRef(int ref){ 101 | this.refs.add(new Integer(ref)); 102 | } 103 | 104 | public String toString(){ 105 | String ret = "\nWay(" + this.id +"): "; 106 | ret += this.wheelchair >= 0 ? "(wheelchair)" : "(non-wheelchair)"; 107 | ret += "\nRefs:"; 108 | for(Integer ref: refs){ 109 | ret += "\n " + ref.intValue(); 110 | } 111 | return ret; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/graph/LatLonPos.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.graph; 2 | 3 | /** 4 | * A class to maintain a coordinate consisting of latitude and longitude, with 5 | * a given level. 6 | * 7 | * @author Paul Smith 8 | * 9 | */ 10 | public class LatLonPos { 11 | private double lat; 12 | private double lon; 13 | 14 | // Float.MAX_VALUE == undefined! 15 | private float level; 16 | 17 | // planet radius in meters 18 | private static final int r = 6378137; 19 | // meters per degree 20 | private static final double scale = (Math.PI * r)/180.0; 21 | 22 | /** 23 | * Constructor to create a 0/0/undefined level coordinate. 24 | */ 25 | public LatLonPos() { 26 | this.lat = 0.0; 27 | this.lon = 0.0; 28 | this.level = Float.MAX_VALUE; 29 | } 30 | 31 | /** 32 | * Constructor to create a coordinate with given parameters. 33 | * 34 | * @param lat the latitude 35 | * @param lon the longitude 36 | * @param level the level 37 | */ 38 | public LatLonPos(double lat, double lon, float level) { 39 | super(); 40 | this.lat = lat; 41 | this.lon = lon; 42 | this.level = level; 43 | } 44 | 45 | public double getLat() { 46 | return lat; 47 | } 48 | 49 | public double getLon() { 50 | return lon; 51 | } 52 | 53 | public float getLevel() { 54 | return level; 55 | } 56 | 57 | public void setLat(double lat) { 58 | this.lat = lat; 59 | } 60 | 61 | public void setLon(double lon) { 62 | this.lon = lon; 63 | } 64 | 65 | public void setLevel(float level) { 66 | this.level = level; 67 | } 68 | 69 | /** 70 | * Return the x value of this coordinate in meters concerning the mercator 71 | * projection. 72 | * 73 | * @return x value in meters 74 | */ 75 | public double getMercatorX(){ 76 | // source: http://mathworld.wolfram.com/MercatorProjection.html 77 | double x = lon; 78 | // translate into meters 79 | x*=scale; 80 | return x; 81 | } 82 | 83 | /** 84 | * Return the y value of this coordinate in meters concerning the mercator 85 | * projection. 86 | * 87 | * @return y value in meters 88 | */ 89 | public double getMercatorY(){ 90 | // source: http://mathworld.wolfram.com/MercatorProjection.html 91 | double y = 0.5*Math.log( 92 | (1+Math.sin(Math.toRadians(lat))) 93 | /(1-Math.sin(Math.toRadians(lat))) ); 94 | // rad to degrees 95 | y = Math.toDegrees(y); 96 | // translate into meters 97 | y*=scale; 98 | return y; 99 | } 100 | 101 | public void moveIntoDirection(LatLonPos nextNode, double factor){ 102 | // First step: Do Mercator Projection with latitude. 103 | lat = lat + (nextNode.lat - lat)*factor; 104 | lon = lon + (nextNode.lon - lon)*factor; 105 | } 106 | 107 | /** 108 | * Create a clone of this Object. 109 | * 110 | * @return the new clone 111 | */ 112 | public LatLonPos clone(){ 113 | LatLonPos ret = new LatLonPos(); 114 | ret.setLat(lat); 115 | ret.setLon(lon); 116 | ret.setLevel(level); 117 | return ret; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/gui/Calibrator.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.gui; 2 | 3 | import android.app.Activity; 4 | import android.content.SharedPreferences; 5 | import android.os.Bundle; 6 | import android.view.SurfaceView; 7 | import android.view.ViewGroup.LayoutParams; 8 | import android.widget.LinearLayout; 9 | import android.widget.SeekBar; 10 | import android.widget.SeekBar.OnSeekBarChangeListener; 11 | import android.widget.TextView; 12 | import de.uvwxy.footpath.R; 13 | import de.uvwxy.footpath.core.StepDetection; 14 | import de.uvwxy.footpath.core.StepTrigger; 15 | 16 | /** 17 | * This Activity is used to calibrate the parameters concerning step detection 18 | * 19 | * @author Paul Smith 20 | * 21 | */ 22 | public class Calibrator extends Activity implements StepTrigger { 23 | public static final String CALIB_DATA = "CALIBDATA"; 24 | private StepDetection stepDetection; 25 | 26 | PaintBoxHistory svHistory; 27 | 28 | // GUI 29 | TextView tvPeak = null; 30 | TextView tvFilter = null; 31 | TextView tvTimeout = null; 32 | SeekBar sbPeak = null; 33 | SeekBar sbFilter = null; 34 | SeekBar sbTimeout = null; 35 | 36 | float peak; // threshold for step detection 37 | float a; // value for low pass filter 38 | int step_timeout_ms; // distance in ms between each step 39 | 40 | OnSeekBarChangeListener sbListener = new OnSeekBarChangeListener(){ 41 | 42 | @Override 43 | public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) { 44 | if(arg0.equals(sbPeak)){ 45 | peak = sbPeak.getProgress()/10.0f; 46 | stepDetection.setPeak(peak); 47 | tvPeak.setText("Set peak value: (" + peak + ")"); 48 | } else if(arg0.equals(sbFilter)){ 49 | a = sbFilter.getProgress()/100.0f; 50 | stepDetection.setA(a); 51 | tvFilter.setText("Set filter value: (" + a + ")"); 52 | } else if(arg0.equals(sbTimeout)){ 53 | step_timeout_ms = sbTimeout.getProgress(); 54 | stepDetection.setStep_timeout_ms(step_timeout_ms); 55 | tvTimeout.setText("Set step timeout: (" + step_timeout_ms + ")"); 56 | } 57 | } 58 | 59 | @Override 60 | public void onStartTrackingTouch(SeekBar arg0) {} 61 | 62 | @Override 63 | public void onStopTrackingTouch(SeekBar arg0) {} 64 | 65 | }; 66 | 67 | private void loadSettings(){ 68 | a = getSharedPreferences(CALIB_DATA,0).getFloat("a", 0.5f); 69 | peak = getSharedPreferences(CALIB_DATA,0).getFloat("peak", 0.5f); 70 | step_timeout_ms = getSharedPreferences(CALIB_DATA,0).getInt("timeout", 666); 71 | 72 | // Update GUI elements 73 | sbPeak.setProgress((int)(peak*10)); 74 | sbFilter.setProgress((int)(a*100)); 75 | sbTimeout.setProgress(step_timeout_ms); 76 | 77 | tvPeak.setText("Set peak value: (" + peak + ")"); 78 | tvFilter.setText("Set filter value: (" + a + ")"); 79 | tvTimeout.setText("Set step timeout: (" + step_timeout_ms + ")"); 80 | } 81 | 82 | private void saveSettings(){ 83 | // Save current values to settings 84 | SharedPreferences settings = getSharedPreferences(CALIB_DATA, 0); 85 | SharedPreferences.Editor editor = settings.edit(); 86 | editor.putFloat("a", a); 87 | editor.putFloat("peak", peak); 88 | editor.putInt("timeout",step_timeout_ms); 89 | // Apply changes 90 | editor.commit(); 91 | } 92 | 93 | @Override 94 | public void dataHookAcc(long nowMs, double x, double y, double z) {} 95 | 96 | @Override 97 | public void dataHookComp(long nowMs, double x, double y, double z) {} 98 | 99 | @Override 100 | public void timedDataHook(long nowMs, double[] acc, double[] comp) {svHistory.addTriple(nowMs, acc);} 101 | 102 | @Override 103 | public void trigger(long nowMs, double compDir) {svHistory.addStepTS(nowMs);} 104 | 105 | /** Called when the activity is first created. */ 106 | @Override 107 | public void onCreate(Bundle savedInstanceState) { 108 | super.onCreate(savedInstanceState); 109 | setContentView(R.layout.calibrator); 110 | 111 | tvPeak = (TextView) findViewById(R.id.tvPeak); 112 | tvFilter = (TextView) findViewById(R.id.tvFilter); 113 | tvTimeout = (TextView) findViewById(R.id.tvTimeout); 114 | 115 | sbPeak = (SeekBar) findViewById(R.id.sbPeak); 116 | sbFilter = (SeekBar) findViewById(R.id.sbFilter); 117 | sbTimeout = (SeekBar) findViewById(R.id.sbTimeout); 118 | 119 | // Load settings after creation of GUI-elements, to set their values 120 | loadSettings(); 121 | stepDetection = new StepDetection(this, this, a, peak, step_timeout_ms); 122 | // Add OnSeekBarChangeListener after creation of step detection, because object is used 123 | sbPeak.setOnSeekBarChangeListener(sbListener); 124 | sbFilter.setOnSeekBarChangeListener(sbListener); 125 | sbTimeout.setOnSeekBarChangeListener(sbListener); 126 | 127 | LinearLayout linLayout = (LinearLayout) findViewById(R.id.LinearLayout01); // get pointer to layout 128 | SurfaceView svOld = (SurfaceView) findViewById(R.id.svHistory); // get SurfaceView defined in xml 129 | LayoutParams lpHistory = svOld.getLayoutParams(); // get its layout params 130 | 131 | long samples_per_second = 1000/stepDetection.INTERVAL_MS; 132 | int history_in_seconds = 4; 133 | int samples_per_history = (int)(history_in_seconds * samples_per_second); 134 | 135 | // create PaintBox (-24.0 to 24.0, 100 entries) 136 | svHistory = new PaintBoxHistory(this, 48.0, samples_per_history, history_in_seconds); 137 | 138 | linLayout.removeView(svOld); // and remove surface view from layout 139 | linLayout.addView(svHistory, lpHistory); // add surface view clone to layout 140 | 141 | } 142 | 143 | @Override 144 | public void onPause() { 145 | super.onPause(); 146 | saveSettings(); 147 | stepDetection.unload(); 148 | } 149 | 150 | @Override 151 | public void onDestroy() { 152 | super.onDestroy(); 153 | } 154 | 155 | @Override 156 | public void onResume() { 157 | super.onResume(); 158 | loadSettings(); 159 | stepDetection.load(); 160 | } 161 | 162 | } -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/gui/Loader.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.gui; 2 | 3 | import java.io.IOException; 4 | 5 | import org.xmlpull.v1.XmlPullParserException; 6 | 7 | import android.app.Activity; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.content.SharedPreferences; 11 | import android.content.res.Resources.NotFoundException; 12 | import android.location.Location; 13 | import android.location.LocationListener; 14 | import android.location.LocationManager; 15 | import android.os.Bundle; 16 | import android.util.Log; 17 | import android.view.View; 18 | import android.view.View.OnClickListener; 19 | import android.widget.AdapterView; 20 | import android.widget.ArrayAdapter; 21 | import android.widget.Button; 22 | import android.widget.CheckBox; 23 | import android.widget.EditText; 24 | import android.widget.Spinner; 25 | import android.widget.Toast; 26 | import android.widget.AdapterView.OnItemSelectedListener; 27 | import de.uvwxy.footpath.R; 28 | import de.uvwxy.footpath.Rev; 29 | import de.uvwxy.footpath.graph.Graph; 30 | import de.uvwxy.footpath.graph.GraphNode; 31 | import de.uvwxy.footpath.graph.LatLonPos; 32 | 33 | /** 34 | * 35 | * @author Paul Smith 36 | * 37 | */ 38 | public class Loader extends Activity { 39 | 40 | public static final String LOADER_SETTINGS = "FootPathSettings"; 41 | 42 | // GRAPH 43 | private static Graph g; // Holds the data structure 44 | private String nodeFrom; // Node name to start from, i.e. "5052" 45 | private int closestNodeID; // Node ID if closest node was found via GPS 46 | private String nodeTo; // Node name to navigate to 47 | private int iNodeFrom = 0; // Selected index from spFrom 48 | private int iNodeTo = 0; // Selected index from spTo 49 | private String[] rooms = null; // Array of all room names added to drop down lists 50 | private LocationManager locationManager; 51 | 52 | // GUI 53 | private Spinner spFrom = null; // Drop down lists 54 | private Spinner spTo = null; 55 | private Button bGo = null; // Buttons 56 | private Button bLoad = null; 57 | private Button bSave = null; 58 | private Button bCalibrate = null; 59 | private Button bGPS = null; 60 | private Button bQRCode = null; 61 | private EditText et01 = null; // EditText, body height 62 | private CheckBox cbStairs = null; // Check boxes, for route selection mode 63 | private CheckBox cbElevator = null; 64 | private CheckBox cbOutside = null; 65 | private CheckBox cbLog = null; 66 | private CheckBox cbAudio = null; 67 | private ArrayAdapter adapter1 = null; // Adapter to manage drop down lists 68 | private ArrayAdapter adapter2 = null; 69 | 70 | // LISTENERS 71 | OnItemSelectedListener spinnerListener = new OnItemSelectedListener() { 72 | @Override 73 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 74 | // List from: save selected name and position in list 75 | if (parent.equals(spFrom)) { 76 | nodeFrom = (String) spFrom.getSelectedItem(); 77 | iNodeFrom = position; 78 | } 79 | // List from: save selected name and position in list 80 | if (parent.equals(spTo)) { 81 | nodeTo = (String) spTo.getSelectedItem(); 82 | iNodeTo = position; 83 | } 84 | 85 | } 86 | 87 | @Override 88 | public void onNothingSelected(AdapterView arg0) { 89 | shortToast("You have to selected nothing"); 90 | } 91 | }; 92 | 93 | OnClickListener onListener = new OnClickListener() { 94 | 95 | @Override 96 | public void onClick(View v) { 97 | // Distinguish which button was pressed 98 | if (v.equals(bGo)) { 99 | if(nodeFrom.equals(nodeTo)){ 100 | shortToast("Please.... " + nodeFrom + " to " + nodeTo + "?"); 101 | return; 102 | } 103 | startNavigation(); 104 | } else if(v.equals(bLoad)){ 105 | // Load values from settings, second parameter is passed if value is not found 106 | int tNodeFrom = getSharedPreferences(LOADER_SETTINGS,0).getInt("nodeFrom", 0); 107 | int tNodeTo = getSharedPreferences(LOADER_SETTINGS,0).getInt("nodeTo", 0); 108 | float sizeIncm = getSharedPreferences(LOADER_SETTINGS,0).getFloat("sizeIncm", 191.0f); 109 | boolean stairs = getSharedPreferences(LOADER_SETTINGS,0).getBoolean("stairs", true); 110 | boolean elevator = getSharedPreferences(LOADER_SETTINGS,0).getBoolean("elevator", true); 111 | boolean outside = getSharedPreferences(LOADER_SETTINGS,0).getBoolean("outside", true); 112 | // Update GUI elements corresponding to variables 113 | cbStairs.setChecked(stairs); 114 | cbElevator.setChecked(elevator); 115 | cbOutside.setChecked(outside); 116 | spFrom.setSelection(tNodeFrom, true); 117 | spTo.setSelection(tNodeTo, true); 118 | et01.setText("" + sizeIncm); 119 | } else if(v.equals(bSave)){ 120 | // Save current values to settings 121 | SharedPreferences settings = getSharedPreferences(LOADER_SETTINGS, 0); 122 | SharedPreferences.Editor editor = settings.edit(); 123 | editor.putInt("nodeFrom", iNodeFrom); 124 | editor.putInt("nodeTo", iNodeTo); 125 | editor.putFloat("sizeIncm", Float.parseFloat(et01.getText().toString())); 126 | editor.putBoolean("stairs", cbStairs.isChecked()); 127 | editor.putBoolean("elevator", cbElevator.isChecked()); 128 | editor.putBoolean("outside", cbOutside.isChecked()); 129 | // Apply changes 130 | editor.commit(); 131 | } else if(v.equals(bCalibrate)){ 132 | Intent intenCalibrator = new Intent(Loader.this, Calibrator.class); 133 | startActivityForResult(intenCalibrator, 2); 134 | } else if(v.equals(bGPS)){ 135 | bGPS.setEnabled(false); 136 | bGPS.setText("Wait for fix"); 137 | initGPS(); 138 | } else if(v.equals(bQRCode)){ 139 | // Source: http://code.google.com/p/zxing/wiki/ScanningViaIntent 140 | Intent intent = new Intent("com.google.zxing.client.android.SCAN"); 141 | intent.setPackage("com.google.zxing.client.android"); 142 | intent.putExtra("SCAN_MODE", "QR_CODE_MODE"); 143 | startActivityForResult(intent, 0); 144 | } 145 | } 146 | }; 147 | 148 | LocationListener locationListener = new LocationListener() { 149 | // Called when a new location is found by the GPS location provider. 150 | public void onLocationChanged(Location location) { 151 | GraphNode closestNode; 152 | LatLonPos pos; 153 | String nodeName; 154 | longToast("GPS location found, searching for nearest node"); 155 | 156 | pos = new LatLonPos(location.getLatitude(),location.getLongitude(),0); 157 | // Third parameter set to false, such that only outdoor nodes are accepted 158 | closestNode = g.getClosestNodeToLatLonPos(pos, 0, false, 17); 159 | 160 | if(closestNode != null){ 161 | closestNodeID = closestNode.getId(); 162 | nodeName = (closestNode.getName()==null)?"" + closestNodeID : closestNode.getName(); 163 | longToast("Closest Node is " + nodeName + "\n\n Please select target\n" + 164 | "The selected room as lcation will be ignored."); 165 | bGPS.setText(nodeName); 166 | locationManager.removeUpdates(this); 167 | bGPS.setEnabled(true); 168 | } else { 169 | // As the level is hard coded to 0, this should never appear, 170 | // as long as all maps have a level beginning at 0. 171 | double dist = g.getClosestDistanceToNode(pos, 0, false); 172 | longToast("No node found on this level (0)!\n\nDistance is " + dist + " meters"); 173 | } 174 | 175 | } 176 | 177 | public void onStatusChanged(String provider, int status, Bundle extras) {} 178 | 179 | public void onProviderEnabled(String provider) {} 180 | 181 | public void onProviderDisabled(String provider) {} 182 | }; 183 | 184 | 185 | // Navigator needs static access to graph 186 | public static Graph getGraph(){ 187 | return g; 188 | } 189 | 190 | /** Called when the activity is first created. */ 191 | @Override 192 | public void onCreate(Bundle savedInstanceState) { 193 | super.onCreate(savedInstanceState); 194 | setContentView(R.layout.selectroom); 195 | 196 | // Create new graph 197 | g = new Graph(); 198 | // And add layer(s) of ways 199 | try { 200 | g.addToGraphFromXMLResourceParser(this.getResources().getXml(R.xml.map1ug)); 201 | g.addToGraphFromXMLResourceParser(this.getResources().getXml(R.xml.mapeg)); 202 | g.addToGraphFromXMLResourceParser(this.getResources().getXml(R.xml.map1og)); 203 | g.addToGraphFromXMLResourceParser(this.getResources().getXml(R.xml.map2og)); 204 | g.addToGraphFromXMLResourceParser(this.getResources().getXml(R.xml.map3og)); 205 | // g.addToGraphFromXMLResourceParser(this.getResources().getXml(R.xml.parkinglot)); 206 | g.mergeNodes(); 207 | rooms = g.getRoomList(); 208 | } catch (NotFoundException e) { 209 | longToast("Error: resource not found:\n\n" + e); 210 | } catch (XmlPullParserException e) { 211 | longToast("Error: xml error:\n\n" + e); 212 | } catch (IOException e) { 213 | longToast("Error: io error:\n\n" + e); 214 | } 215 | 216 | // GUI - Create references to elements on the screen 217 | spFrom = (Spinner) findViewById(R.id.Spinner01); 218 | spTo = (Spinner) findViewById(R.id.Spinner02); 219 | bGo = (Button) findViewById(R.id.btnGo); 220 | bLoad = (Button) findViewById(R.id.btnLoad); 221 | bSave = (Button) findViewById(R.id.btnSave); 222 | bCalibrate = (Button) findViewById(R.id.btnCalibrate); 223 | bGPS = (Button) findViewById(R.id.btnGPS); 224 | bQRCode = (Button) findViewById(R.id.btnQR); 225 | et01 = (EditText) findViewById(R.id.EditText01); 226 | cbStairs = (CheckBox) findViewById(R.id.cbStairs); 227 | cbElevator = (CheckBox) findViewById(R.id.cbElevators); 228 | cbOutside = (CheckBox) findViewById(R.id.cbOutside); 229 | cbLog = (CheckBox) findViewById(R.id.cbLog); 230 | cbAudio = (CheckBox) findViewById(R.id.cbAudio); 231 | this.setTitle("Footpath r(" + Rev.rev.substring(0,8) + ")"); 232 | 233 | // Drop down lists: create entries of room names from rooms 234 | adapter1 = new ArrayAdapter(this, android.R.layout.simple_spinner_item, rooms); 235 | adapter1.setDropDownViewResource(android.R.layout.simple_spinner_item); 236 | adapter2 = new ArrayAdapter(this, android.R.layout.simple_spinner_item, rooms); 237 | adapter2.setDropDownViewResource(android.R.layout.simple_spinner_item); 238 | 239 | spFrom.setAdapter(adapter1); 240 | spTo.setAdapter(adapter2); 241 | 242 | // Set select/click listeners 243 | spFrom.setOnItemSelectedListener(spinnerListener); 244 | spTo.setOnItemSelectedListener(spinnerListener); 245 | bGo.setOnClickListener(onListener); 246 | bLoad.setOnClickListener(onListener); 247 | bSave.setOnClickListener(onListener); 248 | bCalibrate.setOnClickListener(onListener); 249 | bGPS.setOnClickListener(onListener); 250 | bQRCode.setOnClickListener(onListener); 251 | 252 | locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); 253 | } 254 | 255 | @Override 256 | public void onResume() { 257 | super.onResume(); 258 | } 259 | 260 | public void onActivityResult(int requestCode, int resultCode, Intent intent) { 261 | // Source: http://code.google.com/p/zxing/wiki/ScanningViaIntent 262 | 263 | Log.i("FOOTPATH", "requestCode = " + requestCode); 264 | Log.i("FOOTPATH", "resultCode = " + resultCode); 265 | 266 | if (requestCode == 0) { 267 | if (resultCode == RESULT_OK) { 268 | String contents = intent.getStringExtra("SCAN_RESULT"); 269 | String format = intent.getStringExtra("SCAN_RESULT_FORMAT"); 270 | 271 | // Handle successful scan 272 | // FORMAT: "http://......?....&fN=&tN=&..... 273 | 274 | if(!format.equals("QR_CODE")){ 275 | this.longToast("Scan result was not a QR Code"); 276 | return; 277 | } 278 | 279 | String[] split = contents.split("\\?"); 280 | 281 | if(split.length!=2){ 282 | this.longToast("URL: " + contents + "\n\n is in the wrong format!"); 283 | return; 284 | } 285 | 286 | int progress = 0; 287 | String sVarString = split[1]; 288 | String[] sVars = sVarString.split("\\&"); 289 | 290 | for(String s : sVars){ 291 | if(s.split("=")[0].equals("fN")){ 292 | this.nodeFrom = s.split("=")[1]; 293 | progress++; 294 | } else if(s.split("=")[0].equals("tN")){ 295 | this.nodeTo = s.split("=")[1]; 296 | progress++; 297 | } 298 | } 299 | 300 | for(int i = 0; i < rooms.length; i++){ 301 | if(rooms[i].equals(nodeFrom)){ 302 | this.spFrom.setSelection(i); 303 | } 304 | if(rooms[i].equals(nodeTo)){ 305 | this.spTo.setSelection(i); 306 | } 307 | } 308 | 309 | if(progress==2){ 310 | if(nodeFrom.equals(nodeTo)){ 311 | longToast("Even if the URL was correct, you will not be going far from " + nodeFrom + " to " + nodeTo); 312 | } 313 | longToast("Starting navigation from " + nodeFrom + " to " + nodeTo); 314 | startNavigation(); 315 | } 316 | } else if (resultCode == RESULT_CANCELED) { 317 | // Handle cancel 318 | } 319 | } else if (requestCode == 1){ 320 | if(resultCode == RESULT_CANCELED){ 321 | longToast("No Route found!"); 322 | } 323 | } 324 | } 325 | 326 | @Override 327 | public void onPause() { 328 | super.onPause(); 329 | bGPS.setEnabled(true); 330 | locationManager.removeUpdates(locationListener); 331 | } 332 | 333 | private void shortToast(String s) { 334 | Toast.makeText(this, s, Toast.LENGTH_SHORT).show(); 335 | } 336 | 337 | private void longToast(String s) { 338 | Toast.makeText(this, s, Toast.LENGTH_LONG).show(); 339 | } 340 | 341 | private void initGPS(){ 342 | // Register the listener with the Location Manager to receive location updates 343 | locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener); 344 | longToast("Waiting for GPS fix\n\nPlease wait for a notification to continue.\n\nYou can select a destination."); 345 | } 346 | private void startNavigation(){ 347 | Log.i("FOOTPATH", "Starting navigation intent"); 348 | // Create intent for navigation 349 | Intent intentNavigator = new Intent(Loader.this, Navigator.class); 350 | // Add values to be passed to navigator 351 | intentNavigator.putExtra("from", nodeFrom); 352 | intentNavigator.putExtra("fromId", closestNodeID); 353 | intentNavigator.putExtra("to", nodeTo); 354 | intentNavigator.putExtra("stairs", cbStairs.isChecked()); 355 | intentNavigator.putExtra("elevator", cbElevator.isChecked()); 356 | intentNavigator.putExtra("outside", cbOutside.isChecked()); 357 | intentNavigator.putExtra("log", cbLog.isChecked()); 358 | intentNavigator.putExtra("audio", cbAudio.isChecked()); 359 | // Source: http://www.pedometersaustralia.com/g/13868/measure-step-length-.html 360 | intentNavigator.putExtra("stepLength", Float.parseFloat(et01.getText().toString()) * 0.415f); 361 | // Start intent for navigation 362 | startActivityForResult(intentNavigator, 1); 363 | } 364 | } -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/gui/Navigator.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.gui; 2 | 3 | import java.io.IOException; 4 | import java.util.LinkedList; 5 | import java.util.Stack; 6 | 7 | import android.app.Activity; 8 | import android.os.Bundle; 9 | import android.util.Log; 10 | import android.view.SurfaceView; 11 | import android.view.View; 12 | import android.view.View.OnClickListener; 13 | import android.view.ViewGroup.LayoutParams; 14 | import android.view.Window; 15 | import android.view.WindowManager; 16 | import android.widget.Button; 17 | import android.widget.LinearLayout; 18 | import android.widget.ZoomControls; 19 | import de.uvwxy.footpath.R; 20 | import de.uvwxy.footpath.ToolBox; 21 | import de.uvwxy.footpath.core.NPConfig; 22 | import de.uvwxy.footpath.core.Positioner; 23 | import de.uvwxy.footpath.core.Positioner_OnlineBestFit; 24 | import de.uvwxy.footpath.core.Positioner_OnlineFirstFit; 25 | import de.uvwxy.footpath.core.StepDetection; 26 | import de.uvwxy.footpath.core.StepTrigger; 27 | import de.uvwxy.footpath.graph.Graph; 28 | import de.uvwxy.footpath.graph.GraphEdge; 29 | import de.uvwxy.footpath.graph.GraphNode; 30 | import de.uvwxy.footpath.graph.LatLonPos; 31 | import de.uvwxy.footpath.log.AudioWriter; 32 | import de.uvwxy.footpath.log.DataLogger; 33 | /** 34 | * 35 | * @author Paul Smith 36 | * 37 | */ 38 | public class Navigator extends Activity implements StepTrigger { 39 | public static final String LOG_DIR = "routelog/"; 40 | 41 | // ######################################################################### 42 | // ######################### Fields 'n Listener ############################ 43 | // ######################################################################### 44 | 45 | // GUI Elements 46 | private PaintBoxMap pbMap; // Objects to handle the graphics 47 | private Button btnRecalc; 48 | private Button btnSwitchFit; 49 | private Graph g; // Reference to graph 50 | 51 | 52 | // Route information 53 | private String nodeFrom; // Node we will start from, i.e. "5052" 54 | private int nodeFromId = 0; // This is used if we choose nearest location from GPS fix 55 | private String nodeTo; // Node we plan to end up with 56 | private boolean staircase; 57 | private boolean elevator; 58 | private boolean outside; 59 | private LinkedList navPathEdges; // Contains path with corrected compass bearings 60 | // used by PaintBoxMap to paint the path 61 | private LinkedList simplifiedEdges; 62 | private LinkedList tempEdges; // Stores the original edges on path 63 | // Needs to be global: is used for logging in onResume() 64 | private double navPathLen = 0.0; // Total length of path 65 | private double naiveStairsWidth = 0.25; // Naive amount in meters to use as stairs step length 66 | 67 | 68 | // Navigation 69 | private double acceptanceWidth = 42.0; // Amount of derivation allowed for compassValue to path 70 | private StepDetection stepDetection; 71 | private Positioner posBestFit = null; // Object to do another progress estimation 72 | private Positioner posFirstFit = null; 73 | private NPConfig confBestFit = null; 74 | private NPConfig confFirstFit = null; 75 | private NPConfig conf = null; 76 | 77 | // Progress information 78 | private boolean isNavigating = false; // Set to false when nodeTo is reached 79 | private int totalStepsWalked = 0; // Total number of detected steps 80 | 81 | 82 | // Runtime information 83 | private double compassValue = -1.0; 84 | 85 | private LinkedList zVarHistory = new LinkedList(); // store the variance of each step 86 | private int historySize = 64; // Back log of last 64 values to calculate variance 87 | private double[] x_History = new double[historySize]; 88 | private double[] y_History = new double[historySize]; 89 | private double[] z_History = new double[historySize]; 90 | private int historyPtr = 0; 91 | 92 | 93 | // Logging 94 | private DataLogger logger; 95 | private boolean log = false; 96 | private boolean logAudio = false; 97 | private AudioWriter avwCapture; 98 | 99 | 100 | // Listeners 101 | OnClickListener onClick = new OnClickListener(){ 102 | 103 | @Override 104 | public void onClick(View arg0) { 105 | if(arg0.equals(btnRecalc)){ 106 | if(true){ 107 | ((Positioner_OnlineFirstFit) posFirstFit).recalcPos(); 108 | } 109 | 110 | } else if (arg0.equals(btnSwitchFit)){ 111 | if ( btnSwitchFit.getText().equals("Switch to First Fit Algorithm")){ 112 | btnSwitchFit.setText("Switch to Best Fit Algorithm"); 113 | conf = confFirstFit; 114 | } else { 115 | btnSwitchFit.setText("Switch to First Fit Algorithm"); 116 | conf = confBestFit; 117 | } 118 | } 119 | 120 | } 121 | 122 | }; 123 | 124 | // ######################################################################### 125 | // ######################### Getters 'n Setters ############################ 126 | // ######################################################################### 127 | 128 | public LinkedList getNavPathEdges(){ 129 | return navPathEdges; 130 | } 131 | public double getAcceptanceWidth() { 132 | return this.acceptanceWidth; 133 | } 134 | 135 | // last compass value 136 | public double getCompassValue() { 137 | return compassValue; 138 | } 139 | 140 | public GraphEdge getCurrentEdge(NPConfig conf){ 141 | if(conf.npPointer >= navPathEdges.size()){ 142 | return navPathEdges.get(navPathEdges.size()-1); 143 | } 144 | GraphEdge ret = navPathEdges.get(conf.npPointer); 145 | return ret; 146 | } 147 | 148 | public float getCurrentFloorLevel() { 149 | return getLastSeenNode(conf).getLevel(); 150 | } 151 | 152 | public double getEstimatedStepLength(){ 153 | double pathLength = getNavPathWalked(); 154 | double totalSteps = getTotalStepsWalked(); 155 | for(int i = 0; i < conf.npPointer; i++){ 156 | GraphEdge edge = navPathEdges.get(i); 157 | if(edge.isStairs()){ 158 | if(edge.getSteps()==-1){ 159 | pathLength -= edge.getLen(); 160 | totalSteps -= edge.getLen()/naiveStairsWidth; 161 | } else if(edge.getSteps() > 0){ 162 | pathLength -= edge.getLen(); 163 | totalSteps -= edge.getSteps(); 164 | } 165 | } 166 | } 167 | 168 | return pathLength/totalSteps; 169 | 170 | } 171 | 172 | public double getNaiveStairsWidth(){ 173 | return naiveStairsWidth; 174 | } 175 | 176 | public GraphNode getLastSeenNode(NPConfig conf){ 177 | GraphEdge currentEdge = getCurrentEdge(conf); 178 | GraphEdge previousEdge = getPreviousEdge(conf); 179 | if(previousEdge == null){ // no previous edge, thus this is the first edge 180 | return getRouteBegin(); 181 | } 182 | // last seen node is node which is in current and previous edge 183 | GraphNode ret = currentEdge.getNode0(); 184 | if(!previousEdge.contains(ret)){ 185 | ret = currentEdge.getNode1(); 186 | } 187 | return ret; 188 | } 189 | 190 | public double getNavPathLen() { 191 | return navPathLen; 192 | } 193 | 194 | public double getNavPathLenLeft(){ 195 | return navPathLen - getNavPathWalked(); 196 | } 197 | 198 | public double getNavPathDir() { 199 | if(conf.npPointer >= navPathEdges.size()){ 200 | return navPathEdges.get(navPathEdges.size()-1).getCompDir(); 201 | } 202 | return navPathEdges.get(conf.npPointer).getCompDir(); 203 | } 204 | 205 | // returns remaining meters on edge 206 | public double getNavPathEdgeLenLeft() { 207 | // catch route end, return -1.0 208 | if (conf.npPointer >= navPathEdges.size()) 209 | return -1.0; 210 | return navPathEdges.get(conf.npPointer).getLen() - conf.npCurLen; 211 | } 212 | 213 | 214 | // return show far we have walked on the path 215 | public double getNavPathWalked(){ 216 | double len = 0.0; 217 | // sum all traversed edges 218 | for(int i = 0; i < conf.npPointer; i++){ 219 | len += navPathEdges.get(i).getLen(); 220 | } 221 | // and how far we have walked on current edge 222 | len += conf.npCurLen; 223 | return len; 224 | } 225 | 226 | public LatLonPos getPosition() { 227 | return getPosition(conf); 228 | } 229 | 230 | // estimated(?) position of user 231 | public LatLonPos getPosition(NPConfig conf) { 232 | LatLonPos ret = new LatLonPos(); 233 | GraphNode lastSeenNode = getLastSeenNode(conf); 234 | GraphEdge currentEdge = getCurrentEdge(conf); 235 | GraphNode nextNode = currentEdge.getNode0().equals(lastSeenNode)? currentEdge.getNode1() : currentEdge.getNode0(); 236 | 237 | // catch route end, return destination 238 | if (conf.npPointer >= navPathEdges.size()) { 239 | GraphNode lastNode = this.getRouteEnd(); 240 | ret.setLat(lastNode.getLat()); 241 | ret.setLon(lastNode.getLon()); 242 | ret.setLevel(lastNode.getLevel()); 243 | return ret; 244 | } 245 | 246 | ret.setLevel(lastSeenNode.getLevel()); 247 | ret.setLat(lastSeenNode.getLat()); 248 | ret.setLon(lastSeenNode.getLon()); 249 | 250 | // move pos into direction; amount of traveled m on edge 251 | ret.moveIntoDirection(nextNode.getPos(), conf.npCurLen/navPathEdges.get(conf.npPointer).getLen()); 252 | return ret; 253 | } 254 | 255 | public GraphEdge getPreviousEdge(NPConfig conf){ 256 | if(conf.npPointer == 0){ 257 | // no previous edge 258 | return null; 259 | } 260 | return navPathEdges.get(conf.npPointer - 1); 261 | } 262 | 263 | public GraphNode getRouteBegin() { 264 | if(nodeFromId == 0){ 265 | return g.getNodeFromName(nodeFrom); 266 | } else { 267 | return g.getNode(nodeFromId); 268 | } 269 | } 270 | 271 | public GraphNode getRouteEnd() { 272 | return g.getNodeFromName(nodeTo); 273 | } 274 | 275 | // length of each step 276 | public double getStepLengthInMeters() { 277 | return conf.npStepSize; 278 | } 279 | 280 | // total steps walked 281 | public int getTotalStepsWalked() { 282 | return totalStepsWalked; 283 | } 284 | 285 | // total steps not roughly in correct direction 286 | public int getUnmatchedSteps() { 287 | return conf.npUnmatchedSteps; 288 | } 289 | 290 | // are we navigating? 291 | public boolean isNavigating() { 292 | return isNavigating; 293 | } 294 | 295 | /** 296 | * Returns the variance from the back log for x values 297 | * @return the variance for x 298 | */ 299 | public double getVarianceOfX(){ 300 | return varianceOfSet(x_History); 301 | } 302 | /** 303 | * Returns the variance from the back log for y values 304 | * @return the variance for y 305 | */ 306 | public double getVarianceOfY(){ 307 | return varianceOfSet(y_History); 308 | } 309 | /** 310 | * Returns the variance from the back log for z values 311 | * @return the variance for z 312 | */ 313 | public double getVarianceOfZ(){ 314 | return varianceOfSet(z_History); 315 | } 316 | 317 | public void setNavigating(boolean b){ 318 | this.isNavigating = b; 319 | } 320 | 321 | // ######################################################################### 322 | // ########################## Step/Data Callbacks ########################## 323 | // ######################################################################### 324 | 325 | @Override 326 | public void trigger(long now_ms, double compDir) { 327 | this.totalStepsWalked++; 328 | if (!isNavigating) { 329 | // Destination was reached 330 | return; 331 | } 332 | 333 | if(log){ 334 | logger.logStep(now_ms, compDir); 335 | } 336 | 337 | posBestFit.addStep(compDir); 338 | posFirstFit.addStep(compDir); 339 | 340 | Log.i("FOOTPATH", "posBestFit: " + posBestFit.getProgress()); 341 | Log.i("FOOTPATH", "posFirstFit: " + posFirstFit.getProgress()); 342 | if(log){ 343 | // Write location to file after detected step 344 | LatLonPos bestPos = getPosition(confBestFit); 345 | LatLonPos firstPos = getPosition(confFirstFit); 346 | logger.logPosition(now_ms, bestPos.getLat(), bestPos.getLon(), posBestFit.getProgress()/this.navPathLen 347 | , firstPos.getLat(), firstPos.getLon(), posFirstFit.getProgress()/this.navPathLen); 348 | } 349 | } 350 | 351 | @Override 352 | public void dataHookAcc(long now_ms, double x, double y, double z){ 353 | // add values to history (for variance) 354 | addTriple(x, y, z); 355 | if(log){ 356 | logger.logRawAcc(now_ms, x, y, z); 357 | } 358 | } 359 | 360 | @Override 361 | public void dataHookComp(long now_ms, double x, double y, double z){ 362 | if(log){ 363 | logger.logRawCompass(now_ms, x, y, z); 364 | } 365 | compassValue = ToolBox.lowpassFilter(compassValue, x, 0.5); 366 | } 367 | 368 | @Override 369 | public void timedDataHook(long now_ms, double[] acc, double[] comp){ 370 | double varZ = getVarianceOfZ(); 371 | zVarHistory.add(new Double(acc[2])); 372 | 373 | if(log){ 374 | logger.logTimedVariance(now_ms, varZ); 375 | } 376 | if(log){ 377 | // Write Compass and Accelerometer data 378 | logger.logTimedAcc(now_ms, acc[2]); 379 | logger.logTimedCompass(now_ms, comp[0]); 380 | } 381 | } 382 | 383 | 384 | // ######################################################################### 385 | // ######################## Activity Life Cycle ############################ 386 | // ######################################################################### 387 | 388 | 389 | /** Called when the activity is first created. */ 390 | @Override 391 | public void onCreate(Bundle savedInstanceState) { 392 | super.onCreate(savedInstanceState); 393 | requestWindowFeature(Window.FEATURE_NO_TITLE); 394 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 395 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 396 | setContentView(R.layout.displayroute); 397 | 398 | Stack navPathStack; 399 | 400 | // Get location and destination 401 | 402 | nodeFrom = this.getIntent().getStringExtra("from"); 403 | nodeFromId = this.getIntent().getIntExtra("fromId",0); 404 | nodeTo = this.getIntent().getStringExtra("to"); 405 | 406 | // Get reference to graph object 407 | this.g = Loader.getGraph(); 408 | staircase = this.getIntent().getBooleanExtra("stairs", true); 409 | elevator = this.getIntent().getBooleanExtra("elevator", false); 410 | outside = this.getIntent().getBooleanExtra("outside", true); 411 | log = this.getIntent().getBooleanExtra("log", false); 412 | logAudio = this.getIntent().getBooleanExtra("audio", false); 413 | 414 | // calculate route 415 | if(nodeFromId==0){ 416 | navPathStack = this.g.getShortestPath(nodeFrom, nodeTo, staircase, elevator, outside); 417 | logger = new DataLogger(this, System.currentTimeMillis(), nodeFrom, nodeTo); 418 | } else { 419 | navPathStack = this.g.getShortestPath(nodeFromId, nodeTo, staircase, elevator, outside); 420 | logger = new DataLogger(this, System.currentTimeMillis(), "" + nodeFromId, nodeTo); 421 | } 422 | 423 | if(navPathStack != null){ // no route found! 424 | // The navPathStack consists of the correct order of nodes on the path 425 | // From these nodes the corresponding edge is used to get the original 426 | // data connected to it. What has to be recalculated is the initial bearing 427 | // because this is depending on the order of nodes being passed. 428 | 429 | // List to store the new edges in 430 | tempEdges = new LinkedList(); 431 | // Get first node. This is always the 'left' node, when considering 432 | // a path going from left to right. 433 | GraphNode node0 = navPathStack.pop(); 434 | 435 | while(!navPathStack.isEmpty()){ 436 | // Get 'right' node 437 | GraphNode node1 = navPathStack.pop(); 438 | // Get Edge connecting 'left' and 'right' nodes 439 | GraphEdge origEdge = g.getEdge(node0,node1); 440 | 441 | // Get data which remains unchanged 442 | double len = origEdge.getLen(); 443 | short wheelchair = origEdge.getWheelchair(); 444 | float level = origEdge.getLevel(); 445 | boolean indoor = origEdge.isIndoor(); 446 | 447 | // Direction has to be recalculated 448 | double dir = g.getInitialBearing(node0.getLat(), node0.getLon(), node1.getLat(), node1.getLon()); 449 | 450 | // Create new edge 451 | GraphEdge tempEdge = new GraphEdge(node0, node1,len,dir,wheelchair,level,indoor); 452 | // Update additional values 453 | tempEdge.setElevator(origEdge.isElevator()); 454 | tempEdge.setStairs(origEdge.isStairs()); 455 | tempEdge.setSteps(origEdge.getSteps()); 456 | tempEdges.add(tempEdge); 457 | 458 | // Update path length 459 | navPathLen += origEdge.getLen(); 460 | 461 | // 'right' node is new 'left' node 462 | node0 = node1; 463 | } 464 | 465 | Log.i("FOOTPATH", "Number of edges before merge: " + tempEdges.size()); 466 | 467 | // Now that we have the correct order of nodes, and initial bearings of edges 468 | // we look for successive edges with little difference in their bearing 469 | // to simplify the path, having less but longer edges 470 | 471 | // Allow a difference of 5 degrees to both sides 472 | double diff = 8.0; 473 | simplifiedEdges = new LinkedList(); 474 | // The current edge to find equaling edges to 475 | GraphEdge edge_i = null; 476 | // The first node of current edge 477 | GraphNode node_i_0 = null; 478 | // This will be the last node of the last edge equaling edge_i 479 | GraphNode node_x_1 = null; 480 | 481 | // Data to sum up for needed merge; 482 | short wheelchair; 483 | float level; 484 | boolean indoor; 485 | boolean stairs; 486 | boolean elevator; 487 | int steps; 488 | int last_i = -1; 489 | // Iterate over all edges 490 | for (int i = 0; i < tempEdges.size(); i++){ 491 | edge_i = tempEdges.get(i); 492 | node_i_0 = tempEdges.get(i).getNode0(); 493 | level = edge_i.getLevel(); 494 | indoor = edge_i.isIndoor(); 495 | stairs = edge_i.isStairs(); 496 | elevator = edge_i.isElevator(); 497 | steps = edge_i.getSteps(); 498 | wheelchair = edge_i.getWheelchair(); 499 | Log.i("FOOTPATH", "Edge (" + (i+1) + "/" + tempEdges.size() + ") dir: " + edge_i.getCompDir()); 500 | last_i = i; 501 | for (int j = i + 1; j < tempEdges.size(); j++){ 502 | GraphEdge edge_j = tempEdges.get(j); 503 | // Only merge edges if they are identical in their characteristics 504 | if(edge_i.getLevel() == edge_j.getLevel() 505 | && edge_i.isElevator() == edge_j.isElevator() 506 | && edge_i.isIndoor() == edge_j.isIndoor() 507 | && edge_i.isStairs() == edge_j.isStairs() 508 | && Positioner.isInRange(edge_i.getCompDir(), tempEdges.get(j).getCompDir(), diff)){ 509 | Log.i("FOOTPATH", "Adding " + edge_j.getCompDir()); 510 | // Edge_i and edge_j can be merged 511 | // Save last node1 of last edge_j equaling edge_i 512 | node_x_1 = edge_j.getNode1(); 513 | 514 | // Set number of steps only if defined (-1 := undefined, but steps) 515 | if(steps != -1){ 516 | // only change 0 or defined steps 517 | if(edge_j.getSteps() != -1){ 518 | steps += edge_j.getSteps(); 519 | } else { 520 | // edge_j has no defined step count, thus set to undefined 521 | steps = -1; 522 | } 523 | } 524 | } else { 525 | Log.i("FOOTPATH", "Not Merging " + edge_j.getCompDir()); 526 | // Edge_i and edge_j can not be merged 527 | // Merge possible previously found edges and add them 528 | 529 | // Point to latest edge to try matching from 530 | i = j-1; 531 | 532 | // Nothing can be merged, leave edge_i as is 533 | if(node_x_1 == null){ 534 | Log.i("FOOTPATH", "Created same edge i " + edge_i.getLen() + " and direction " + edge_i.getCompDir()); 535 | // Add same edge_i 536 | simplifiedEdges.add(edge_i); 537 | break; 538 | } else { 539 | // Add modified new edge 540 | double bearing = g.getInitialBearing(node_i_0.getLat(), node_i_0.getLon(), node_x_1.getLat(), node_x_1.getLon()); 541 | double len = g.getDistance(node_i_0.getLat(), node_i_0.getLon(), node_x_1.getLat(), node_x_1.getLon()); 542 | GraphEdge tempEdge = new GraphEdge(node_i_0, node_x_1, len, bearing, wheelchair, level, indoor); 543 | tempEdge.setElevator(elevator); 544 | tempEdge.setStairs(stairs); 545 | tempEdge.setSteps(steps); 546 | simplifiedEdges.add(tempEdge); 547 | Log.i("FOOTPATH", "Created edge with length of " + tempEdge.getLen() + " and direction " + tempEdge.getCompDir()); 548 | // Reset last node to null to distinguish if something has to be merged 549 | node_x_1 = null; 550 | break; 551 | } 552 | } 553 | } 554 | } 555 | 556 | if(last_i != -1){ 557 | for(int i = last_i; i < tempEdges.size(); i++){ 558 | Log.i("FOOTPATH", "Adding missing edges"); 559 | simplifiedEdges.add(tempEdges.get(i)); 560 | } 561 | } 562 | 563 | // Set current path 564 | navPathEdges = simplifiedEdges; 565 | Log.i("FOOTPATH", "EDGES: " + navPathEdges); 566 | Log.i("FOOTPATH", "Number of edges after merge: " + navPathEdges.size()); 567 | 568 | // Get handles to button and zoom controls and save their configuration 569 | ZoomControls zoomControls = (ZoomControls) findViewById(R.id.zoomCtrl); 570 | btnRecalc = (Button) findViewById(R.id.btnRecalc); 571 | btnSwitchFit = (Button) findViewById(R.id.btnSwitchFit); 572 | 573 | // Load fancy graphics 574 | pbMap = new PaintBoxMap(this, this); 575 | // REPLACING :: has to be done in order of appearance on display (top to bottom) 576 | replaceSurfaceView(pbMap, (SurfaceView) findViewById(R.id.svPath)); // svPath with pbNavigator 577 | 578 | btnRecalc.setOnClickListener(onClick); 579 | btnSwitchFit.setOnClickListener(onClick); 580 | zoomControls.setOnZoomInClickListener(new OnClickListener() { 581 | public void onClick(View v) { 582 | pbMap.zoomIn(); 583 | } 584 | }); 585 | 586 | zoomControls.setOnZoomOutClickListener(new OnClickListener() { 587 | public void onClick(View v) { 588 | pbMap.zoomOut(); 589 | } 590 | }); 591 | 592 | confBestFit = new NPConfig(); 593 | confBestFit.npCurLen = 0.0; 594 | confBestFit.npLastMatchedStep = -1; 595 | confBestFit.npMatchedSteps = 0; 596 | confBestFit.npPointer = 0; 597 | // /100.0f -> cm to m 598 | confBestFit.npStepSize = this.getIntent().getFloatExtra("stepLength", 191.0f/0.415f/100.0f)/100.0f; 599 | confBestFit.npUnmatchedSteps = 0; 600 | 601 | confFirstFit = new NPConfig(confBestFit); 602 | 603 | // Create correct pointer to chosen positioner 604 | conf = confBestFit; 605 | 606 | double a = getSharedPreferences(Calibrator.CALIB_DATA,0).getFloat("a", 0.5f); 607 | double peak = getSharedPreferences(Calibrator.CALIB_DATA,0).getFloat("peak", 0.5f); 608 | int step_timeout_ms = getSharedPreferences(Calibrator.CALIB_DATA,0).getInt("timeout", 666); 609 | 610 | stepDetection = new StepDetection(this, this, a, peak, step_timeout_ms); 611 | 612 | posBestFit = new Positioner_OnlineBestFit(this, this.navPathEdges, confBestFit); 613 | posFirstFit = new Positioner_OnlineFirstFit(this, this.navPathEdges, confFirstFit); 614 | 615 | 616 | 617 | setNavigating( true ); 618 | } else { // navPathStack was null 619 | this.setResult(RESULT_CANCELED); 620 | this.finish(); 621 | } 622 | } 623 | 624 | @Override 625 | public void onPause() { 626 | super.onPause(); 627 | stepDetection.unload(); 628 | if(log){ 629 | // Log to info file 630 | logger.logInfo("a: " + stepDetection.getA()); 631 | logger.logInfo("peak: " + stepDetection.getPeak()); 632 | logger.logInfo("step timeout (ms): " + stepDetection.getStep_timeout_ms()); 633 | logger.logInfo("Recognised steps: " + this.totalStepsWalked); 634 | logger.logInfo("Estimated stepsize: " + this.getEstimatedStepLength()); 635 | logger.logInfo("Output of columns:"); 636 | logger.stopLogging(); 637 | if(logAudio){ 638 | avwCapture.stopCapture(); 639 | avwCapture.unregisterCapture(); 640 | } 641 | } 642 | 643 | } 644 | 645 | @Override 646 | public void onDestroy() { 647 | super.onDestroy(); 648 | } 649 | 650 | @Override 651 | public void onResume() { 652 | super.onResume(); 653 | if(log){ 654 | logger.startLogging(); 655 | // Only log route if files opened correctly 656 | if(logger.started()){ 657 | 658 | if(log){ 659 | for(GraphEdge e: navPathEdges){ 660 | logger.logSimpleRoute(e.getNode0().getLat(), e.getNode0().getLon()); 661 | } 662 | GraphEdge e = navPathEdges.get(navPathEdges.size()-1); 663 | logger.logSimpleRoute(e.getNode1().getLat(), e.getNode1().getLon()); 664 | } 665 | if(log){ 666 | for(GraphEdge e: tempEdges){ 667 | logger.logRoute(e.getNode0().getLat(), e.getNode0().getLon()); 668 | } 669 | GraphEdge e = tempEdges.get(tempEdges.size()-1); 670 | logger.logRoute(e.getNode1().getLat(), e.getNode1().getLon()); 671 | } 672 | 673 | 674 | 675 | 676 | // Create files for AudioWrite here, with correct file name as other log files 677 | if(nodeFromId==0){ 678 | if(logAudio){ 679 | avwCapture = new AudioWriter("" + logger.getRouteId() + "_" + nodeFrom + "_" + nodeTo +"/", "video.3gp"); 680 | } 681 | } else { 682 | if(logAudio){ 683 | avwCapture = new AudioWriter("" + logger.getRouteId() + "_" + nodeFromId + "_" + nodeTo +"/", "video.3gp"); 684 | } 685 | } 686 | 687 | if(logAudio){ 688 | try { 689 | avwCapture.registerCapture(); 690 | avwCapture.startCapture(); 691 | } catch (IllegalStateException e) { 692 | e.printStackTrace(); 693 | } catch (IOException e) { 694 | e.printStackTrace(); 695 | } 696 | } 697 | } 698 | 699 | } 700 | 701 | stepDetection.load(); 702 | 703 | } 704 | 705 | 706 | 707 | // ######################################################################### 708 | // ############################## Functions ################################ 709 | // ######################################################################### 710 | 711 | private void replaceSurfaceView(SurfaceView svNew, SurfaceView svOld) { 712 | LayoutParams layParam = svOld.getLayoutParams(); 713 | LinearLayout ll = (LinearLayout) findViewById(R.id.ll01); 714 | ll.removeView(svOld); 715 | ll.addView(svNew, layParam); 716 | } 717 | 718 | 719 | /** 720 | * Add values to backlog (for variance) 721 | * @param x Sensor x value 722 | * @param y Sensor y value 723 | * @param z Sensor z value 724 | */ 725 | private void addTriple(double x, double y, double z) { 726 | x_History[(historyPtr + 1) % historySize] = x; 727 | y_History[(historyPtr + 1) % historySize] = y; 728 | z_History[(historyPtr + 1) % historySize] = z; 729 | historyPtr++; 730 | } 731 | /** 732 | * Calculates the mean of a given set 733 | * @param set the set 734 | * @return the mean value 735 | */ 736 | private double meanOfSet(double[] set) { 737 | double res = 0.0; 738 | for (double i : set) { 739 | res += i; 740 | } 741 | return res / set.length; 742 | 743 | } 744 | /** 745 | * Calculates the variance of a given set 746 | * @param set the set 747 | * @return the variance value 748 | */ 749 | private double varianceOfSet(double[] set) { 750 | double res = 0.0; 751 | double mean = meanOfSet(set); 752 | for (double i : set) { 753 | res += (i - mean) * (i - mean); 754 | } 755 | return res / set.length; 756 | } 757 | 758 | // -1 := left 759 | // 0 := straight on 760 | // 1 := right 761 | public int getNextTurn(){ 762 | if(conf.npPointer == navPathEdges.size()-1){ 763 | // Walking on the last edge, go straight on 764 | return 0; 765 | } 766 | 767 | if(Positioner.isInRange(navPathEdges.get(conf.npPointer).getCompDir(),navPathEdges.get(conf.npPointer+1).getCompDir(),10)){ 768 | // +- 10 degrees is straight on 769 | return 0; 770 | } 771 | if(Positioner.isInRange(navPathEdges.get(conf.npPointer).getCompDir()-90,navPathEdges.get(conf.npPointer+1).getCompDir(),90)){ 772 | // This edge -90 degrees is in range of next edge 773 | // -> next turn is left turn 774 | return -1; 775 | } 776 | // Else its a right turn 777 | return 1; 778 | } 779 | 780 | } 781 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/gui/PaintBoxHistory.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.gui; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import de.uvwxy.footpath.ToolBox; 8 | import de.uvwxy.paintbox.PaintBox; 9 | 10 | /** 11 | * 12 | * @author Paul Smith 13 | * 14 | */ 15 | class PaintBoxHistory extends PaintBox { 16 | //private Context context; 17 | 18 | private int historySize; 19 | private double[] x_History; 20 | private double[] y_History; 21 | private double[] z_History; 22 | private long[] time_History; 23 | private int historyPtr = 0; 24 | private double valueRange = Double.NEGATIVE_INFINITY; 25 | private int drawWidth = 0; 26 | private int drawHeight = 0; 27 | //private double scale_x = 1; 28 | private double scale_y = 1; 29 | private int offset_y = 0; 30 | boolean once = true; 31 | 32 | private int seconds = 1; // # of seconds to show on screen 33 | private int num_steps = 0; 34 | 35 | /** 36 | * 37 | * @param context 38 | * the context under which is painted 39 | * @param valueRange 40 | * range of values to be display around zero 41 | * @param seconds 42 | * @param tvSteps 43 | * @param drawWidth 44 | * the width of the paintable area 45 | * @param drawHeight 46 | * the height of the paintable area 47 | */ 48 | public PaintBoxHistory(Context context, double valueRange, int historySize, int seconds) { 49 | super(context); 50 | // save to have e.g. access to asserts 51 | // this.context = context; 52 | this.valueRange = valueRange; 53 | this.historySize = historySize; 54 | x_History = new double[historySize]; 55 | y_History = new double[historySize]; 56 | z_History = new double[historySize]; 57 | time_History = new long[historySize]; 58 | this.seconds = seconds; 59 | } 60 | 61 | private int getPosOnScreen(long x_ms, long current_ms) { 62 | long diff_ms = current_ms - x_ms; 63 | 64 | // addition because diff_ms is negative! 65 | int res = (int) (drawWidth - (((double) drawWidth / (double) seconds) / 1000.0) * diff_ms); 66 | return res; 67 | } 68 | 69 | @Override 70 | protected void onDraw(Canvas canvas) { 71 | if (once) { 72 | setDimensions(); 73 | } 74 | canvas.drawColor(Color.WHITE); 75 | canvas.drawLine(0, offset_y, drawWidth, offset_y, ToolBox.myPaint(1, Color.BLACK)); 76 | Paint paint = ToolBox.myPaint(2, Color.RED); 77 | paint.setTextSize(40.0f); 78 | canvas.drawText("Steps: " + num_steps, 10, getHeight()-40, paint); 79 | long uptime_ms = System.currentTimeMillis(); 80 | 81 | for(long ts : tenLastSteps){ 82 | canvas.drawLine(getPosOnScreen(ts, uptime_ms), 0, 83 | getPosOnScreen(ts, uptime_ms), drawHeight, ToolBox.myPaint(2, Color.RED)); 84 | } 85 | 86 | drawDataSet(canvas, x_History, ToolBox.myPaint(2, Color.RED), uptime_ms); 87 | drawDataSet(canvas, y_History, ToolBox.myPaint(2, Color.GREEN), uptime_ms); 88 | drawDataSet(canvas, z_History, ToolBox.myPaint(2, Color.BLUE), uptime_ms); 89 | 90 | canvas.drawText("" + varianceOfSet(x_History), 10, 10, ToolBox.myPaint(2, Color.BLACK)); 91 | canvas.drawText("" + varianceOfSet(y_History), 10, 32, ToolBox.myPaint(2, Color.BLACK)); 92 | canvas.drawText("" + varianceOfSet(z_History), 10, 54, ToolBox.myPaint(2, Color.BLACK)); 93 | } 94 | 95 | private void drawDataSet(Canvas canvas, double[] set, Paint paint, long uptime_ms) { 96 | int item0, item1; 97 | for (int i = 0; i < historySize - 1; i++) { 98 | item0 = (historyPtr + 1 + i) % historySize; 99 | item1 = (historyPtr + 2 + i) % historySize; 100 | double y1 = -set[item0] * scale_y + offset_y; 101 | double y2 = -set[item1] * scale_y + offset_y; 102 | 103 | canvas.drawLine(getPosOnScreen(time_History[item0], uptime_ms), (int) y1, 104 | getPosOnScreen(time_History[item1], uptime_ms), (int) y2, paint); 105 | 106 | } 107 | } 108 | 109 | public void setDimensions() { 110 | this.drawWidth = this.getWidth(); 111 | this.drawHeight = this.getHeight(); 112 | // scale_x = this.drawWidth / historySize; 113 | scale_y = this.drawHeight / valueRange; 114 | offset_y = this.drawHeight / 2; 115 | } 116 | 117 | /** 118 | * Call this timed 119 | * @param t 120 | * @param x 121 | * @param y 122 | * @param z 123 | */ 124 | public void addTriple(long t, double[] acc) { 125 | x_History[(historyPtr + 1) % historySize] = acc[0]; 126 | y_History[(historyPtr + 1) % historySize] = acc[1]; 127 | z_History[(historyPtr + 1) % historySize] = acc[2]; 128 | time_History[(historyPtr + 1) % historySize] = t; 129 | historyPtr++; 130 | } 131 | 132 | /** 133 | * Calculate the mean of a given array 134 | * 135 | * @param set 136 | * the input data 137 | * @return mean of the input data 138 | */ 139 | private double meanOfSet(double[] set) { 140 | double res = 0.0; 141 | for (double i : set) { 142 | res += i; 143 | } 144 | return res / set.length; 145 | 146 | } 147 | 148 | /** 149 | * Calculate the variance of agiven array 150 | * 151 | * @param set 152 | * the input data 153 | * @return variance of the input data 154 | */ 155 | private double varianceOfSet(double[] set) { 156 | double res = 0.0; 157 | double mean = meanOfSet(set); 158 | for (double i : set) { 159 | res += (i - mean) * (i - mean); 160 | } 161 | return res / set.length; 162 | } 163 | 164 | 165 | private int stepHistorySize = 10; 166 | private long[] tenLastSteps = new long[stepHistorySize]; 167 | private int shPointer = 0; 168 | 169 | public void addStepTS(long ts){ 170 | num_steps++; 171 | tenLastSteps[shPointer % stepHistorySize] = ts; // add value to values_history 172 | shPointer++; 173 | shPointer = shPointer % stepHistorySize; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/gui/PaintBoxMap.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.gui; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.net.HttpURLConnection; 8 | import java.net.MalformedURLException; 9 | import java.net.URL; 10 | import java.util.LinkedList; 11 | 12 | import android.content.Context; 13 | import android.content.SharedPreferences; 14 | import android.graphics.Bitmap; 15 | import android.graphics.BitmapFactory; 16 | import android.graphics.Canvas; 17 | import android.graphics.Color; 18 | import android.graphics.Matrix; 19 | import android.graphics.Paint; 20 | import android.graphics.RectF; 21 | import android.os.Environment; 22 | import android.util.Log; 23 | import de.uvwxy.footpath.R; 24 | import de.uvwxy.footpath.ToolBox; 25 | import de.uvwxy.footpath.core.Positioner; 26 | import de.uvwxy.footpath.graph.GraphEdge; 27 | import de.uvwxy.footpath.graph.LatLonPos; 28 | import de.uvwxy.paintbox.PaintBox; 29 | 30 | /** 31 | * 32 | * @author Paul Smith 33 | * 34 | */ 35 | class PaintBoxMap extends PaintBox { 36 | private static final String MAP_SETTINGS = "PaintBoxMap"; 37 | 38 | private Tile[] tiles; // array to store osm tiles 39 | private Bitmap arrow; // png, this is the user position 40 | private Bitmap arrowred; // user position in red 41 | private Bitmap stairs; // icon to show stairs on map 42 | 43 | private Context context; 44 | private Navigator navigator; // object to get data from (location, bearing,..) 45 | 46 | private LinkedList edges; // all edges on the path, in right order 47 | private LatLonPos lbBound; // left bottom position of bounding box (lat/lon) 48 | private LatLonPos rtBound; // rigt top position of bounding box (lat/lon) 49 | 50 | private float gScale = 1.0f; // global scaling, pressing the zoom buttons will change this 51 | private float scaleFactor = 0.6f; // value added/removed when changing zoom level 52 | 53 | private boolean runOnce = true; // needed to create/load resources once 54 | 55 | public PaintBoxMap(Context context, Navigator navigator) { 56 | super(context); 57 | this.context = context; 58 | this.navigator = navigator; 59 | // Load saved zoom level 60 | this.gScale = context.getSharedPreferences(MAP_SETTINGS,0).getFloat("gScale",1.0f); 61 | } 62 | 63 | // create lbBound and rtBound 64 | private void setBoundaries() { 65 | double latMin = Double.POSITIVE_INFINITY; 66 | double latMax = Double.NEGATIVE_INFINITY; 67 | double lonMin = Double.POSITIVE_INFINITY; 68 | double lonMax = Double.NEGATIVE_INFINITY; 69 | for(GraphEdge edge : edges){ // edges contain only edges from path 70 | // but still, almost every node is searched twice ([a,b][b,c][c,d]...) 71 | double n0lat = edge.getNode0().getLat(); 72 | double n1lat = edge.getNode1().getLat(); 73 | double n0lon = edge.getNode0().getLon(); 74 | double n1lon = edge.getNode1().getLon(); 75 | 76 | if(n0lat < latMin) // find minimum lat 77 | latMin = n0lat; 78 | if(n1lat < latMin) 79 | latMin = n1lat; 80 | if(n0lon < lonMin) // find minimum lon 81 | lonMin = n0lon; 82 | if(n1lon < lonMin) 83 | lonMin = n1lon; 84 | 85 | if(n0lat > latMax) // find maximum lat 86 | latMax = n0lat; 87 | if(n1lat > latMax) 88 | latMax = n1lat; 89 | if(n0lon > lonMax) // find maximum lon 90 | lonMax = n0lon; 91 | if(n1lon > lonMax) 92 | lonMax = n1lon; 93 | } 94 | 95 | lbBound = new LatLonPos(latMin, lonMin, -1337); 96 | rtBound = new LatLonPos(latMax, lonMax, -1337); 97 | } 98 | 99 | // load tiles for given zoom level, from sdcard or http 100 | private Tile[] loadTiles(int zoomlevel, LatLonPos lbBoundary, LatLonPos rtBoundary){ 101 | // source: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames 102 | 103 | // find out which tiles to get 104 | int x0 = getTileX(lbBoundary.getLon(), zoomlevel); // point 0 left top 105 | int y0 = getTileY(rtBoundary.getLat(), zoomlevel); 106 | int x1 = getTileX(rtBoundary.getLon(), zoomlevel); // point 1 right top 107 | int y2 = getTileY(lbBoundary.getLat(), zoomlevel); // point 2 left bottom 108 | 109 | int diffX = x1 - x0; 110 | int diffY = y2 - y0; 111 | 112 | int arraySize = (diffX+3)*(diffY+3); 113 | arraySize = arraySize <= 0 ? 1 : arraySize; // fix size if only one tile needed 114 | Tile[] res = new Tile[arraySize]; 115 | 116 | // check if dir exists 117 | File dir = new File(Environment.getExternalStorageDirectory(),"footpath/"); 118 | if(!dir.exists()){ 119 | dir.mkdir(); 120 | } 121 | int counter = 0; 122 | boolean downloadFailed = false; 123 | for(int x = -1; x <= diffX+1; x++){ // x/y = -1 to have some more tiles around the building 124 | for(int y = -1; y <= diffY+1; y++){ // because some parts of the building might be overlapping 125 | // and thus not visible (nicer graphics) 126 | File f = new File(Environment.getExternalStorageDirectory(),"footpath/tile." 127 | + zoomlevel + "." + (x0+x) + "." + (y0+y) + ".png"); 128 | if(f.exists()){ 129 | // file existed -> read it 130 | res[counter] = new Tile(zoomlevel, x0+x, y0+y, BitmapFactory.decodeFile(f.getPath())); 131 | Log.i("FOOTPATH", "Loading from sd footpath/tile." + zoomlevel + "." + (x0+x) + "." + (y0+y) + ".png)"); 132 | } else { 133 | // file did not exist -> download it 134 | URL u = null; 135 | try { 136 | u= new URL("http://tile.openstreetmap.org/" + zoomlevel + "/" + (x0+x) + "/" + (y0+y) + ".png"); 137 | } catch (MalformedURLException e) { 138 | Log.i("FOOTPATH", "URL creation failed (http://tile.openstreetmap.org/" + zoomlevel + "/" + (x0+x) + "/" + (y0+y) + ".png)" + "\n" + e); 139 | } 140 | try { 141 | HttpURLConnection c = (HttpURLConnection)u.openConnection(); 142 | c.setDoInput(true); 143 | InputStream is = c.getInputStream(); 144 | res[counter] = new Tile(zoomlevel, x0+x, y0+y, BitmapFactory.decodeStream(is)); 145 | Log.i("FOOTPATH", "Download succeeded (" + u.toString() + ")"); 146 | // -> and save it 147 | try { 148 | FileOutputStream out = new FileOutputStream(f); 149 | res[counter].getBitmap().compress(Bitmap.CompressFormat.PNG, 90, out); 150 | Log.i("FOOTPATH", "Writing of file suceeded (" + f.toString() + ")"); 151 | } catch (Exception e) { 152 | Log.i("FOOTPATH", "Writing of file failed (" + f.toString() + ")" + "\n" + e); 153 | } 154 | } catch (IOException e) { 155 | Log.i("FOOTPATH", "Download failed (" + u.toString() + ")" + "\n" + e); 156 | downloadFailed = true; 157 | } 158 | } 159 | counter++; 160 | } 161 | } 162 | if(downloadFailed){ 163 | // TODO: Give feedback on failed download of map tiles 164 | } 165 | return res; 166 | } 167 | 168 | private int getTileX(double lon, int zoom) { 169 | return (int)Math.floor( (lon + 180) / 360 * (1< green, else red) 317 | Paint wPaint = edges.get(i).getWheelchair()<0 ? ToolBox.redPaint() : ToolBox.greenPaint(); 318 | 319 | if(edges.get(i).isStairs()){ // draw stairs icon 320 | Matrix m = new Matrix(); 321 | m.setScale(1.0f, 1.0f); 322 | m.postTranslate(globalOffsetX + getPosX(newNodePos, localScale) - stairs.getWidth()/2.0f , 323 | globalOffsetY + getPosY(newNodePos, localScale) - stairs.getHeight()/2.0f); 324 | canvas.drawBitmap(stairs,m,null); 325 | } 326 | canvas.drawLine(globalOffsetX + getPosX(oldNodePos, localScale), // draw path 327 | globalOffsetY + getPosY(oldNodePos, localScale), 328 | globalOffsetX + getPosX(newNodePos, localScale), 329 | globalOffsetY + getPosY(newNodePos, localScale), 330 | wPaint); 331 | 332 | oldNodePos = newNodePos; 333 | 334 | } 335 | } 336 | 337 | float scaleWidth; // displacement to center path: x 338 | float scaleHeight; // displacement to center path: y 339 | float globalOffsetX = 0.0f; // in case we want to "move" path in x 340 | float globalOffsetY = 0.0f; // in case we want to "move" path in y 341 | 342 | private float getPosX(LatLonPos p, double localScale){ 343 | return mercatXToScreen(p.getMercatorX(), localScale); 344 | } 345 | 346 | private float getPosY(LatLonPos p, double localScale){ 347 | return mercatYToScreen(p.getMercatorY(), localScale); 348 | } 349 | 350 | private float mercatXToScreen(double x, double localScale){ // this returns the position relative to the middle of the 351 | double highX = rtBound.getMercatorX(); // width of all data 352 | double lowX = lbBound.getMercatorX(); 353 | double w = (highX - lowX); 354 | double xs = w/2 - (highX - x); // xs = relative position to center of data 355 | xs *=localScale; 356 | return (float)xs; 357 | } 358 | 359 | private float mercatYToScreen(double y, double localScale){ // this returns the position relative to the middle of the 360 | double highY = rtBound.getMercatorY(); // height of all data 361 | double lowY = lbBound.getMercatorY(); 362 | double h = (highY - lowY); 363 | double ys = h/2 - (highY - y); // ys = relative position to center of data 364 | ys *= -1.0*localScale; 365 | return (float)ys; 366 | } 367 | 368 | public void zoomOut() { 369 | if (gScale <= scaleFactor) { 370 | } else { 371 | gScale -= scaleFactor; 372 | SharedPreferences settings = context.getSharedPreferences(MAP_SETTINGS, 0); 373 | SharedPreferences.Editor editor = settings.edit(); 374 | editor.putFloat("gScale", gScale); 375 | editor.commit(); 376 | } 377 | } 378 | 379 | public void zoomIn() { 380 | if (gScale >= 100.0f) { 381 | } else { 382 | gScale += scaleFactor; 383 | SharedPreferences settings = context.getSharedPreferences(MAP_SETTINGS, 0); 384 | SharedPreferences.Editor editor = settings.edit(); 385 | editor.putFloat("gScale", gScale); 386 | editor.commit(); 387 | } 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/gui/Tile.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.gui; 2 | 3 | import android.graphics.Bitmap; 4 | import de.uvwxy.footpath.graph.LatLonPos; 5 | /** 6 | * 7 | * @author Paul Smith 8 | * 9 | */ 10 | public class Tile { 11 | int zoomlevel; 12 | int x; 13 | int y; 14 | Bitmap bitmap; 15 | public Tile(int zoomlevel, int x, int y, Bitmap bitmap) { 16 | this.zoomlevel = zoomlevel; 17 | this.x = x; 18 | this.y = y; 19 | this.bitmap = bitmap; 20 | } 21 | public int getZoomlevel() { 22 | return zoomlevel; 23 | } 24 | public int getX() { 25 | return x; 26 | } 27 | public int getY() { 28 | return y; 29 | } 30 | public Bitmap getBitmap() { 31 | return bitmap; 32 | } 33 | public void setZoomlevel(int zoomlevel) { 34 | this.zoomlevel = zoomlevel; 35 | } 36 | public void setX(int x) { 37 | this.x = x; 38 | } 39 | public void setY(int y) { 40 | this.y = y; 41 | } 42 | public void setBitmap(Bitmap bitmap) { 43 | this.bitmap = bitmap; 44 | } 45 | public LatLonPos getLatLonPosLeftTop(){ 46 | // source: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames 47 | double lon = 0.0; 48 | double lat = 0.0; 49 | double n = Math.pow(2, zoomlevel); 50 | lon = x/n*360.0-180.0; 51 | double lat_rad = Math.atan(Math.sinh(Math.PI*(1.0-2.0*y/n))); 52 | lat = lat_rad * 180.0/Math.PI; 53 | return new LatLonPos(lat,lon,-1337); 54 | } 55 | public LatLonPos getLatLonPosRightBottom(){ 56 | // source: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames 57 | double lon = 0.0; 58 | double lat = 0.0; 59 | double n = Math.pow(2, zoomlevel); 60 | lon = (x+1)/n*360.0-180.0; 61 | double lat_rad = Math.atan(Math.sinh(Math.PI*(1.0-2.0*(y+1)/n))); 62 | lat = lat_rad * 180.0/Math.PI; 63 | return new LatLonPos(lat,lon,-1337); 64 | } 65 | } -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/log/AudioWriter.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.log; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import de.uvwxy.footpath.gui.Navigator; 7 | 8 | import android.media.MediaRecorder; 9 | import android.os.Environment; 10 | 11 | /** 12 | * A class for recording of an audio/video file, with input from the 13 | * microphone/camera . The directory written to is LOG_DIR within the directory 14 | * retrieved from getExternalStorageDirectory(). The file name is given to the 15 | * constructor. 16 | * 17 | * Usage: 18 | * 19 | * o = new AudioWriter() 20 | * o.registerCapture() 21 | * o.startCapture() 22 | * [... magic moment in time ...] 23 | * o.stopCapture() 24 | * o.unregisterCapture() 25 | * 26 | * 27 | * @author Paul Smith 28 | * 29 | */ 30 | public class AudioWriter { 31 | private MediaRecorder recorder; 32 | private String filePath; 33 | 34 | /** 35 | * The only constructor 36 | * 37 | * @param fileName the file name 38 | */ 39 | public AudioWriter(String sub_directory, String fileName) { 40 | File dir = new File(Environment.getExternalStorageDirectory(), 41 | Navigator.LOG_DIR + sub_directory); 42 | dir.mkdir(); 43 | this.filePath = dir.getAbsolutePath() + "/" + fileName; 44 | } 45 | 46 | /** 47 | * Call this to initialize everything needed to capture from AudioSource.MIC 48 | * 49 | * @throws IllegalStateException 50 | * @throws IOException 51 | */ 52 | public void registerCapture() throws IllegalStateException, IOException { 53 | recorder = new MediaRecorder(); 54 | recorder.setAudioSource(MediaRecorder.AudioSource.MIC); 55 | recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 56 | recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); 57 | recorder.setOutputFile(filePath); 58 | recorder.prepare(); 59 | } 60 | 61 | /** 62 | * Call this to start capture 63 | */ 64 | public void startCapture() { 65 | recorder.start(); 66 | } 67 | 68 | /** 69 | * Call this to stop capture 70 | */ 71 | public void stopCapture() { 72 | recorder.stop(); 73 | recorder.reset(); 74 | } 75 | 76 | /** 77 | * Call this to release capture device 78 | */ 79 | public void unregisterCapture() { 80 | recorder.release(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/log/DataLogger.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.log; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.util.List; 5 | 6 | import android.content.BroadcastReceiver; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.IntentFilter; 10 | import android.location.Location; 11 | import android.location.LocationListener; 12 | import android.location.LocationManager; 13 | import android.net.wifi.ScanResult; 14 | import android.net.wifi.WifiManager; 15 | import android.os.Bundle; 16 | import android.util.Log; 17 | import de.uvwxy.footpath.Rev; 18 | 19 | /** 20 | * Interface to log compass, accelerometer, GPS, WiFi and route 21 | * Use startLogging() to register file writers and GPS/WiFi interfaces 22 | * Use log[..](params) from class using the DataLogger interface 23 | * GPS/WiFi Logging will be handled in this object 24 | * Use stopLogging() to close everything mentioned above 25 | * @author Paul Smith 26 | * 27 | */ 28 | public class DataLogger { 29 | // Objects to work with 30 | private long routeID; 31 | private String from; 32 | private String to; 33 | 34 | private boolean started = false; 35 | private boolean accOpen = false; // To check if files have been opened successfully 36 | private boolean compOpen = false; 37 | 38 | private FWriter fwCompass; // Log data to /sdcard/routelog/ 39 | private FWriter fwAccelerometer; 40 | private FWriter fwVariance; 41 | private FWriter fwPosition; 42 | private FWriter fwSteps; 43 | private FWriter fwGPS; 44 | private FWriter fwWifi; 45 | private FWriter fwRawAccel; 46 | private FWriter fwRawCompass; 47 | private FWriter fwRoute; 48 | private FWriter fwSimpleRoute; 49 | private FWriter fwInfo; 50 | private LocationManager locationManager = null; 51 | 52 | private WifiManager wm01; 53 | private WifiReceiver wr01; 54 | private List lScanResult; 55 | 56 | private Context context; 57 | 58 | /* 59 | * Handles writing of GPS data. 60 | */ 61 | LocationListener locationListener = new LocationListener() { 62 | @Override 63 | public void onLocationChanged(Location location) { 64 | try { 65 | fwGPS.openFileOnCard(); 66 | fwGPS.appendLineToFile("" 67 | + System.currentTimeMillis() + "," 68 | + location.getLatitude() + "," 69 | + location.getLongitude()); 70 | fwGPS.closeFileOnCard(); 71 | } catch (FileNotFoundException e) { 72 | e.printStackTrace(); 73 | } 74 | } 75 | @Override 76 | public void onStatusChanged(String provider, int status, Bundle extras) { 77 | } 78 | @Override 79 | public void onProviderEnabled(String provider) { 80 | } 81 | @Override 82 | public void onProviderDisabled(String provider) { 83 | } 84 | 85 | }; 86 | 87 | /** 88 | * A class that receives the scan results of a WiFi scan. After each scan a 89 | * new scan is started. 90 | * 91 | * @author Paul 92 | * 93 | */ 94 | class WifiReceiver extends BroadcastReceiver { 95 | // Wifi 96 | WifiManager wmLocal; 97 | 98 | public WifiReceiver(WifiManager wm01) { 99 | wmLocal = wm01; 100 | } 101 | 102 | @Override 103 | public void onReceive(Context c, Intent intent) { 104 | lScanResult = wm01.getScanResults(); 105 | boolean retry = true; 106 | int tries = 0; 107 | while(retry && tries < 3){ 108 | try { 109 | fwWifi.openFileOnCard(); 110 | fwWifi.appendLineToFile("Time passed (ms): " 111 | + (System.currentTimeMillis())); 112 | retry = false; 113 | } catch (FileNotFoundException e) { 114 | tries++; 115 | } 116 | } 117 | // retry was set to false, can log data 118 | if(!retry){ 119 | for (int i = 0; i < lScanResult.size(); i++) { 120 | fwWifi.appendLineToFile((new Integer(i + 1).toString() 121 | + "." + lScanResult.get(i)).toString()); 122 | } 123 | fwWifi.closeFileOnCard(); 124 | } 125 | // After each scan, start a new scan. 126 | wmLocal.startScan(); 127 | } 128 | } 129 | 130 | public DataLogger(Context context, long routeID, String from, String to){ 131 | this.routeID = routeID; 132 | this.from = from; 133 | this.to = to; 134 | this.context = context; 135 | locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 136 | } 137 | 138 | public void logTimedCompass(long timestamp, double value){ 139 | writeTofwObject(fwCompass, "" + timestamp + ", " + value); 140 | } 141 | public void logTimedVariance(long timestamp, double value){ 142 | writeTofwObject(fwVariance, "" + timestamp + ", " + value); 143 | } 144 | 145 | public void logRawCompass(long timestamp, double x, double y, double z){ 146 | if(!compOpen){ 147 | boolean retry = true; 148 | int tries = 0; 149 | while(retry && tries < 3){ 150 | try { 151 | fwRawCompass.openFileOnCard(); 152 | retry = false; 153 | compOpen = true; 154 | } catch (FileNotFoundException e) { 155 | tries++; 156 | } 157 | } 158 | } 159 | if(compOpen){ 160 | fwRawCompass.appendLineToFile("" + timestamp + ", " + x + ", " + y + ", " + z); 161 | } 162 | } 163 | 164 | public void logTimedAcc(long timestamp, double value){ 165 | writeTofwObject(fwAccelerometer, "" + timestamp + ", " + value); 166 | } 167 | 168 | public void logRawAcc(long timestamp, double x, double y, double z){ 169 | if(!accOpen){ 170 | boolean retry = true; 171 | int tries = 0; 172 | while(retry && tries < 3){ 173 | try { 174 | fwRawAccel.openFileOnCard(); 175 | retry = false; 176 | accOpen = true; 177 | } catch (FileNotFoundException e) { 178 | tries++; 179 | } 180 | } 181 | } 182 | if(accOpen){ 183 | fwRawAccel.appendLineToFile("" + timestamp + ", " + x + ", " + y + ", " + z); 184 | } 185 | } 186 | 187 | public void logPosition(long timestamp, double latBest, double lonBest, double progressBest, 188 | double latFirst, double lonFirst, double progressFirst){ 189 | // NOTE: progress is in [0,1] 190 | writeTofwObject(fwPosition, "" + timestamp + ", " + latBest + ", " + lonBest + ", " + progressBest 191 | + latFirst + ", " + lonFirst + ", " + progressFirst); 192 | } 193 | 194 | public void logRoute(double lat0, double lon0){ 195 | writeTofwObject(fwRoute, "" + lat0 + ", " + lon0); 196 | } 197 | public void logSimpleRoute(double lat0, double lon0){ 198 | writeTofwObject(fwSimpleRoute, "" + lat0 + ", " + lon0); 199 | } 200 | 201 | public void logStep(long timestamp, double direction){ 202 | writeTofwObject(fwSteps, "" + timestamp + ", " + direction); 203 | } 204 | 205 | public void logInfo(String s){ 206 | writeTofwObject(fwInfo, s); 207 | } 208 | 209 | public boolean started(){ 210 | return started; 211 | } 212 | 213 | public long getRouteId(){ 214 | return routeID; 215 | } 216 | 217 | public void startLogging(){ 218 | Log.i("FOOTPATH", "Starting Logging"); 219 | routeID = System.currentTimeMillis(); 220 | createFileObjects(); 221 | 222 | try { 223 | fwRawAccel.openFileOnCard(); 224 | accOpen = true; 225 | fwRawCompass.openFileOnCard(); 226 | compOpen = true; 227 | } catch (FileNotFoundException e) { 228 | e.printStackTrace(); 229 | } 230 | 231 | locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 232 | 0, locationListener); 233 | wm01 = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 234 | wr01 = new WifiReceiver(wm01); 235 | context.registerReceiver(wr01, new IntentFilter( 236 | WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); 237 | Log.i("FOOTPATH", "Registered WifiReceiver"); 238 | wm01.startScan(); 239 | Log.i("FOOTPATH", "Started WiFi Scan"); 240 | started = true; 241 | } 242 | 243 | public void stopLogging(){ 244 | started = false; 245 | locationManager.removeUpdates(locationListener); 246 | context.unregisterReceiver(wr01); 247 | if(accOpen){ 248 | fwRawAccel.closeFileOnCard(); 249 | } 250 | if(compOpen){ 251 | fwRawCompass.closeFileOnCard(); 252 | } 253 | } 254 | 255 | private void createFileObjects() { 256 | fwAccelerometer = new FWriter("" + routeID + "_" + from + "_" + to, "acc.csv"); 257 | fwCompass = new FWriter("" + routeID + "_" + from + "_" + to, "comp.csv"); 258 | fwVariance = new FWriter("" + routeID + "_" + from + "_" + to, "zVar.csv"); 259 | fwPosition = new FWriter("" + routeID + "_" + from + "_" + to, "pos.csv"); 260 | fwSteps = new FWriter("" + routeID + "_" + from + "_" + to, "steps.csv"); 261 | fwGPS = new FWriter("" + routeID + "_" + from + "_" + to, "gps.csv"); 262 | fwWifi = new FWriter("" + routeID + "_" + from + "_" + to, "wifi.csv"); 263 | fwRawAccel = new FWriter("" + routeID + "_" + from + "_" + to, "rawacc.csv"); 264 | fwRawCompass = new FWriter("" + routeID + "_" + from + "_" + to, "rawcomp.csv"); 265 | fwRoute = new FWriter("" + routeID + "_" + from + "_" + to, "route.csv"); 266 | fwSimpleRoute = new FWriter("" + routeID + "_" + from + "_" + to, "simpleroute.csv"); 267 | fwInfo = new FWriter("" + routeID + "_" + from + "_" + to, "info(rev. " + Rev.rev + ").txt"); 268 | } 269 | 270 | private void writeTofwObject(FWriter fw, String data){ 271 | boolean retry = true; 272 | int tries = 0; 273 | while(retry && tries < 3){ 274 | try { 275 | fw.openFileOnCard(); 276 | fw.appendLineToFile(data); 277 | fw.closeFileOnCard(); 278 | retry = false; 279 | } catch (FileNotFoundException e) { 280 | //e.printStackTrace(); 281 | tries++; 282 | } 283 | } 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/de/uvwxy/footpath/log/FWriter.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.footpath.log; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.io.PrintWriter; 8 | 9 | import de.uvwxy.footpath.gui.Navigator; 10 | 11 | import android.os.Environment; 12 | import android.util.Log; 13 | 14 | /** 15 | * A class for opening, writing to and closing of a text file. The directory 16 | * written to is LOG_DIR in the directory retrieved from 17 | * getExternalStorageDirectory(). The file name is given to the constructor. 18 | * 19 | * @author Paul Smith 20 | * 21 | */ 22 | public class FWriter { 23 | PrintWriter p; 24 | FileOutputStream fos; 25 | String fileName; 26 | boolean open = false; 27 | 28 | String sub_directory; 29 | /** 30 | * Constructor 31 | * 32 | * @param fileName 33 | * the file name 34 | */ 35 | public FWriter(String sub_directory, String fileName) { 36 | this.fileName = fileName; 37 | this.sub_directory = sub_directory; 38 | } 39 | 40 | /** 41 | * Creates the directory LOG_DIR on the external storage and opens the file 42 | * for writing. There is no problem if LOG_DIR already exists. 43 | * 44 | * @throws FileNotFoundException 45 | */ 46 | public void openFileOnCard() throws FileNotFoundException { 47 | // check if first dir exists 48 | File dir = new File(Environment.getExternalStorageDirectory(),Navigator.LOG_DIR); 49 | if(!dir.exists()){ 50 | dir.mkdir(); 51 | } 52 | // check if second dir exists 53 | dir = new File(Environment.getExternalStorageDirectory(), 54 | Navigator.LOG_DIR + sub_directory); 55 | if(!dir.exists()){ 56 | dir.mkdir(); 57 | } 58 | // check if file exists 59 | File f = new File (dir, fileName); 60 | if(!f.exists()){ 61 | try { 62 | // create it 63 | f.createNewFile(); 64 | } catch (IOException e) { 65 | Log.i("FOOTPATH", "ERROR: " + e.toString()); 66 | throw new FileNotFoundException("IOException"); 67 | } 68 | } 69 | try { 70 | fos = new FileOutputStream(f, true); 71 | p = new PrintWriter(fos); 72 | } catch (IOException e) { 73 | Log.i("FOOTPATH", "ERROR: " + e.toString()); 74 | throw new FileNotFoundException("IOException"); 75 | } 76 | 77 | open = true; 78 | } 79 | 80 | /** 81 | * Appends a string to a text file followed by a new line 82 | * 83 | * @param data 84 | * the line to append 85 | */ 86 | public void appendLineToFile(String data) { 87 | p.append(data + "\n"); 88 | } 89 | 90 | /** 91 | * Closes the previously opened file.F 92 | */ 93 | public void closeFileOnCard() { 94 | if(open){ 95 | p.close(); 96 | open = false; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/de/uvwxy/paintbox/PaintBox.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.paintbox; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.view.SurfaceHolder; 6 | import android.view.SurfaceView; 7 | 8 | /** 9 | * A class managing the creation of a canvas to draw on. 10 | * 11 | * Usage: 12 | * 13 | * Create a class which overrides onDraw. Once the surface is created onDraw() 14 | * is called in an infinite loop from a PaintThread. Destroying the surface 15 | * stops the background thread calling onDraw(). 16 | * 17 | * @author Paul Smith 18 | * 19 | */ 20 | public abstract class PaintBox extends SurfaceView implements SurfaceHolder.Callback { 21 | 22 | @Override 23 | public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { 24 | } 25 | 26 | @Override 27 | public void surfaceCreated(SurfaceHolder arg0) { 28 | pThread = new PaintThread(getHolder(), this); 29 | pThread.setRunning(true); 30 | pThread.start(); 31 | } 32 | 33 | @Override 34 | public void surfaceDestroyed(SurfaceHolder arg0) { 35 | boolean retry = true; 36 | pThread.setRunning(false); 37 | while (retry) { 38 | try { 39 | pThread.join(); 40 | retry = false; 41 | } catch (InterruptedException e) { 42 | } 43 | } 44 | } 45 | 46 | @Override 47 | protected abstract void onDraw(Canvas canvas); 48 | 49 | PaintThread pThread; 50 | 51 | public PaintBox(Context context) { 52 | super(context); 53 | getHolder().addCallback(this); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/de/uvwxy/paintbox/PaintThread.java: -------------------------------------------------------------------------------- 1 | package de.uvwxy.paintbox; 2 | 3 | import android.graphics.Canvas; 4 | import android.view.SurfaceHolder; 5 | 6 | /** 7 | * A class to create a thread to repaint the graphics. 8 | * 9 | * @author Paul Smith 10 | * 11 | */ 12 | class PaintThread extends Thread { 13 | private SurfaceHolder surfaceHolder; 14 | private PaintBox pBox; 15 | private boolean bRunning = false; 16 | 17 | public PaintThread(SurfaceHolder surfaceHolder, PaintBox pBox) { 18 | this.surfaceHolder = surfaceHolder; 19 | this.pBox = pBox; 20 | } 21 | 22 | public void setRunning(boolean run) { 23 | bRunning = run; 24 | } 25 | 26 | @Override 27 | public void run() { 28 | Canvas c; 29 | while (bRunning) { 30 | c = null; 31 | try { 32 | c = surfaceHolder.lockCanvas(null); 33 | synchronized (surfaceHolder) { 34 | pBox.onDraw(c); 35 | } 36 | } finally { 37 | if (c != null) { 38 | surfaceHolder.unlockCanvasAndPost(c); 39 | } 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /tools/4228_4222.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/tools/4228_4222.png -------------------------------------------------------------------------------- /tools/4228_5056.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/tools/4228_5056.png -------------------------------------------------------------------------------- /tools/README: -------------------------------------------------------------------------------- 1 | Usage: java -jar URL2QR.jar 2 | OR 3 | Usage: java -jar URL2QR.jar 4 | 5 | -------------------------------------------------------------------------------- /tools/URL2QR.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COMSYS/FootPath/d3cfd0d9030e66a3fef30e83161cfc541a984dab/tools/URL2QR.jar -------------------------------------------------------------------------------- /update-revfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function parse_git_branch { 4 | git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/' 5 | } 6 | 7 | function get_git_revision { 8 | git rev-parse `parse_git_branch` 9 | } 10 | 11 | 12 | #echo "branch:" `parse_git_branch` 13 | #echo "sha1: " `get_git_revision` 14 | REVNO=$(get_git_revision) 15 | echo "package de.uvwxy.footpath;public class Rev{public final static String rev = \"$REVNO\";}" > gen/de/uvwxy/footpath/Rev.java 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------