├── .gitignore
├── README.md
├── css
└── font-awesome.min.css
├── gfx
├── douglas_peucker.png
├── gnome_maps.png
├── inovex_logo.svg
├── light-bulb.jpg
├── prediction_correction.png
├── rdp_animation.gif
├── rdp_animation_large.gif
├── route_track.png
├── route_track_mod.png
└── route_track_mod.xcf
├── gpx
├── 3-laender-giro.gpx
├── 3-laender-giro_cleaned.gpx
└── hh_marathon.gpx
├── js
├── libgif.js
├── require.js
└── rubbable.js
├── notebooks
├── 1-gpx.ipynb
├── 2-mplleaflet.ipynb
├── 3-rdp.ipynb
├── 4-pykalman.ipynb
└── gps_utils.py
├── requirements.txt
├── show_slides.sh
├── talk.ipynb
└── talk.slides.html
/.gitignore:
--------------------------------------------------------------------------------
1 | # Temporary and binary files
2 | *~
3 | *.py[cod]
4 | *.so
5 | *.cfg
6 | !setup.cfg
7 | *.orig
8 | *.log
9 | *.pot
10 | __pycache__/*
11 | .cache/*
12 | .*.swp
13 |
14 | # Project files
15 | .ropeproject
16 | .project
17 | .pydevproject
18 | .settings
19 | .idea
20 | .ipynb_checkpoints
21 |
22 | # Package files
23 | *.egg
24 | *.eggs/
25 | .installed.cfg
26 | *.egg-info
27 |
28 | # Unittest and coverage
29 | htmlcov/*
30 | .coverage
31 | .tox
32 | junit.xml
33 | coverage.xml
34 |
35 | # Build and docs folder/files
36 | build/*
37 | dist/*
38 | sdist/*
39 | docs/api/*
40 | docs/_build/*
41 | cover/*
42 | MANIFEST
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Handling GPS data with Python
2 |
3 | [](http://mybinder.org/repo/FlorianWilhelm/gps_data_with_python)
4 |
5 | Slides of my talk "Handling GPS data with Python" at the EuroPython 2016 in Bilbao.
6 |
7 | [Check it out online!](http://nbviewer.jupyter.org/format/slides/github/FlorianWilhelm/gps_data_with_python/blob/master/talk.ipynb#/)
8 |
9 |
--------------------------------------------------------------------------------
/css/font-awesome.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
5 |
--------------------------------------------------------------------------------
/gfx/douglas_peucker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianWilhelm/gps_data_with_python/c2e2611fa4e554a70bafa585488c22fd0030968e/gfx/douglas_peucker.png
--------------------------------------------------------------------------------
/gfx/gnome_maps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianWilhelm/gps_data_with_python/c2e2611fa4e554a70bafa585488c22fd0030968e/gfx/gnome_maps.png
--------------------------------------------------------------------------------
/gfx/inovex_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
27 |
--------------------------------------------------------------------------------
/gfx/light-bulb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianWilhelm/gps_data_with_python/c2e2611fa4e554a70bafa585488c22fd0030968e/gfx/light-bulb.jpg
--------------------------------------------------------------------------------
/gfx/prediction_correction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianWilhelm/gps_data_with_python/c2e2611fa4e554a70bafa585488c22fd0030968e/gfx/prediction_correction.png
--------------------------------------------------------------------------------
/gfx/rdp_animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianWilhelm/gps_data_with_python/c2e2611fa4e554a70bafa585488c22fd0030968e/gfx/rdp_animation.gif
--------------------------------------------------------------------------------
/gfx/rdp_animation_large.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianWilhelm/gps_data_with_python/c2e2611fa4e554a70bafa585488c22fd0030968e/gfx/rdp_animation_large.gif
--------------------------------------------------------------------------------
/gfx/route_track.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianWilhelm/gps_data_with_python/c2e2611fa4e554a70bafa585488c22fd0030968e/gfx/route_track.png
--------------------------------------------------------------------------------
/gfx/route_track_mod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianWilhelm/gps_data_with_python/c2e2611fa4e554a70bafa585488c22fd0030968e/gfx/route_track_mod.png
--------------------------------------------------------------------------------
/gfx/route_track_mod.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianWilhelm/gps_data_with_python/c2e2611fa4e554a70bafa585488c22fd0030968e/gfx/route_track_mod.xcf
--------------------------------------------------------------------------------
/js/libgif.js:
--------------------------------------------------------------------------------
1 | /*
2 | SuperGif
3 |
4 | Example usage:
5 |
6 |
7 |
8 |
16 |
17 | Image tag attributes:
18 |
19 | rel:animated_src - If this url is specified, it's loaded into the player instead of src.
20 | This allows a preview frame to be shown until animated gif data is streamed into the canvas
21 |
22 | rel:auto_play - Defaults to 1 if not specified. If set to zero, a call to the play() method is needed
23 |
24 | Constructor options args
25 |
26 | gif Required. The DOM element of an img tag.
27 | loop_mode Optional. Setting this to false will force disable looping of the gif.
28 | auto_play Optional. Same as the rel:auto_play attribute above, this arg overrides the img tag info.
29 | max_width Optional. Scale images over max_width down to max_width. Helpful with mobile.
30 | on_end Optional. Add a callback for when the gif reaches the end of a single loop (one iteration). The first argument passed will be the gif HTMLElement.
31 | loop_delay Optional. The amount of time to pause (in ms) after each single loop (iteration).
32 | draw_while_loading Optional. Determines whether the gif will be drawn to the canvas whilst it is loaded.
33 | show_progress_bar Optional. Only applies when draw_while_loading is set to true.
34 |
35 | Instance methods
36 |
37 | // loading
38 | load( callback ) Loads the gif specified by the src or rel:animated_src sttributie of the img tag into a canvas element and then calls callback if one is passed
39 | load_url( src, callback ) Loads the gif file specified in the src argument into a canvas element and then calls callback if one is passed
40 |
41 | // play controls
42 | play - Start playing the gif
43 | pause - Stop playing the gif
44 | move_to(i) - Move to frame i of the gif
45 | move_relative(i) - Move i frames ahead (or behind if i < 0)
46 |
47 | // getters
48 | get_canvas The canvas element that the gif is playing in. Handy for assigning event handlers to.
49 | get_playing Whether or not the gif is currently playing
50 | get_loading Whether or not the gif has finished loading/parsing
51 | get_auto_play Whether or not the gif is set to play automatically
52 | get_length The number of frames in the gif
53 | get_current_frame The index of the currently displayed frame of the gif
54 |
55 | For additional customization (viewport inside iframe) these params may be passed:
56 | c_w, c_h - width and height of canvas
57 | vp_t, vp_l, vp_ w, vp_h - top, left, width and height of the viewport
58 |
59 | A bonus: few articles to understand what is going on
60 | http://enthusiasms.org/post/16976438906
61 | http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
62 | http://humpy77.deviantart.com/journal/Frame-Delay-Times-for-Animated-GIFs-214150546
63 |
64 | */
65 | (function (root, factory) {
66 | if (typeof define === 'function' && define.amd) {
67 | define([], factory);
68 | } else if (typeof exports === 'object') {
69 | module.exports = factory();
70 | } else {
71 | root.SuperGif = factory();
72 | }
73 | }(this, function () {
74 | // Generic functions
75 | var bitsToNum = function (ba) {
76 | return ba.reduce(function (s, n) {
77 | return s * 2 + n;
78 | }, 0);
79 | };
80 |
81 | var byteToBitArr = function (bite) {
82 | var a = [];
83 | for (var i = 7; i >= 0; i--) {
84 | a.push( !! (bite & (1 << i)));
85 | }
86 | return a;
87 | };
88 |
89 | // Stream
90 | /**
91 | * @constructor
92 | */
93 | // Make compiler happy.
94 | var Stream = function (data) {
95 | this.data = data;
96 | this.len = this.data.length;
97 | this.pos = 0;
98 |
99 | this.readByte = function () {
100 | if (this.pos >= this.data.length) {
101 | throw new Error('Attempted to read past end of stream.');
102 | }
103 | if (data instanceof Uint8Array)
104 | return data[this.pos++];
105 | else
106 | return data.charCodeAt(this.pos++) & 0xFF;
107 | };
108 |
109 | this.readBytes = function (n) {
110 | var bytes = [];
111 | for (var i = 0; i < n; i++) {
112 | bytes.push(this.readByte());
113 | }
114 | return bytes;
115 | };
116 |
117 | this.read = function (n) {
118 | var s = '';
119 | for (var i = 0; i < n; i++) {
120 | s += String.fromCharCode(this.readByte());
121 | }
122 | return s;
123 | };
124 |
125 | this.readUnsigned = function () { // Little-endian.
126 | var a = this.readBytes(2);
127 | return (a[1] << 8) + a[0];
128 | };
129 | };
130 |
131 | var lzwDecode = function (minCodeSize, data) {
132 | // TODO: Now that the GIF parser is a bit different, maybe this should get an array of bytes instead of a String?
133 | var pos = 0; // Maybe this streaming thing should be merged with the Stream?
134 | var readCode = function (size) {
135 | var code = 0;
136 | for (var i = 0; i < size; i++) {
137 | if (data.charCodeAt(pos >> 3) & (1 << (pos & 7))) {
138 | code |= 1 << i;
139 | }
140 | pos++;
141 | }
142 | return code;
143 | };
144 |
145 | var output = [];
146 |
147 | var clearCode = 1 << minCodeSize;
148 | var eoiCode = clearCode + 1;
149 |
150 | var codeSize = minCodeSize + 1;
151 |
152 | var dict = [];
153 |
154 | var clear = function () {
155 | dict = [];
156 | codeSize = minCodeSize + 1;
157 | for (var i = 0; i < clearCode; i++) {
158 | dict[i] = [i];
159 | }
160 | dict[clearCode] = [];
161 | dict[eoiCode] = null;
162 |
163 | };
164 |
165 | var code;
166 | var last;
167 |
168 | while (true) {
169 | last = code;
170 | code = readCode(codeSize);
171 |
172 | if (code === clearCode) {
173 | clear();
174 | continue;
175 | }
176 | if (code === eoiCode) break;
177 |
178 | if (code < dict.length) {
179 | if (last !== clearCode) {
180 | dict.push(dict[last].concat(dict[code][0]));
181 | }
182 | }
183 | else {
184 | if (code !== dict.length) throw new Error('Invalid LZW code.');
185 | dict.push(dict[last].concat(dict[last][0]));
186 | }
187 | output.push.apply(output, dict[code]);
188 |
189 | if (dict.length === (1 << codeSize) && codeSize < 12) {
190 | // If we're at the last code and codeSize is 12, the next code will be a clearCode, and it'll be 12 bits long.
191 | codeSize++;
192 | }
193 | }
194 |
195 | // I don't know if this is technically an error, but some GIFs do it.
196 | //if (Math.ceil(pos / 8) !== data.length) throw new Error('Extraneous LZW bytes.');
197 | return output;
198 | };
199 |
200 |
201 | // The actual parsing; returns an object with properties.
202 | var parseGIF = function (st, handler) {
203 | handler || (handler = {});
204 |
205 | // LZW (GIF-specific)
206 | var parseCT = function (entries) { // Each entry is 3 bytes, for RGB.
207 | var ct = [];
208 | for (var i = 0; i < entries; i++) {
209 | ct.push(st.readBytes(3));
210 | }
211 | return ct;
212 | };
213 |
214 | var readSubBlocks = function () {
215 | var size, data;
216 | data = '';
217 | do {
218 | size = st.readByte();
219 | data += st.read(size);
220 | } while (size !== 0);
221 | return data;
222 | };
223 |
224 | var parseHeader = function () {
225 | var hdr = {};
226 | hdr.sig = st.read(3);
227 | hdr.ver = st.read(3);
228 | if (hdr.sig !== 'GIF') throw new Error('Not a GIF file.'); // XXX: This should probably be handled more nicely.
229 | hdr.width = st.readUnsigned();
230 | hdr.height = st.readUnsigned();
231 |
232 | var bits = byteToBitArr(st.readByte());
233 | hdr.gctFlag = bits.shift();
234 | hdr.colorRes = bitsToNum(bits.splice(0, 3));
235 | hdr.sorted = bits.shift();
236 | hdr.gctSize = bitsToNum(bits.splice(0, 3));
237 |
238 | hdr.bgColor = st.readByte();
239 | hdr.pixelAspectRatio = st.readByte(); // if not 0, aspectRatio = (pixelAspectRatio + 15) / 64
240 | if (hdr.gctFlag) {
241 | hdr.gct = parseCT(1 << (hdr.gctSize + 1));
242 | }
243 | handler.hdr && handler.hdr(hdr);
244 | };
245 |
246 | var parseExt = function (block) {
247 | var parseGCExt = function (block) {
248 | var blockSize = st.readByte(); // Always 4
249 | var bits = byteToBitArr(st.readByte());
250 | block.reserved = bits.splice(0, 3); // Reserved; should be 000.
251 | block.disposalMethod = bitsToNum(bits.splice(0, 3));
252 | block.userInput = bits.shift();
253 | block.transparencyGiven = bits.shift();
254 |
255 | block.delayTime = st.readUnsigned();
256 |
257 | block.transparencyIndex = st.readByte();
258 |
259 | block.terminator = st.readByte();
260 |
261 | handler.gce && handler.gce(block);
262 | };
263 |
264 | var parseComExt = function (block) {
265 | block.comment = readSubBlocks();
266 | handler.com && handler.com(block);
267 | };
268 |
269 | var parsePTExt = function (block) {
270 | // No one *ever* uses this. If you use it, deal with parsing it yourself.
271 | var blockSize = st.readByte(); // Always 12
272 | block.ptHeader = st.readBytes(12);
273 | block.ptData = readSubBlocks();
274 | handler.pte && handler.pte(block);
275 | };
276 |
277 | var parseAppExt = function (block) {
278 | var parseNetscapeExt = function (block) {
279 | var blockSize = st.readByte(); // Always 3
280 | block.unknown = st.readByte(); // ??? Always 1? What is this?
281 | block.iterations = st.readUnsigned();
282 | block.terminator = st.readByte();
283 | handler.app && handler.app.NETSCAPE && handler.app.NETSCAPE(block);
284 | };
285 |
286 | var parseUnknownAppExt = function (block) {
287 | block.appData = readSubBlocks();
288 | // FIXME: This won't work if a handler wants to match on any identifier.
289 | handler.app && handler.app[block.identifier] && handler.app[block.identifier](block);
290 | };
291 |
292 | var blockSize = st.readByte(); // Always 11
293 | block.identifier = st.read(8);
294 | block.authCode = st.read(3);
295 | switch (block.identifier) {
296 | case 'NETSCAPE':
297 | parseNetscapeExt(block);
298 | break;
299 | default:
300 | parseUnknownAppExt(block);
301 | break;
302 | }
303 | };
304 |
305 | var parseUnknownExt = function (block) {
306 | block.data = readSubBlocks();
307 | handler.unknown && handler.unknown(block);
308 | };
309 |
310 | block.label = st.readByte();
311 | switch (block.label) {
312 | case 0xF9:
313 | block.extType = 'gce';
314 | parseGCExt(block);
315 | break;
316 | case 0xFE:
317 | block.extType = 'com';
318 | parseComExt(block);
319 | break;
320 | case 0x01:
321 | block.extType = 'pte';
322 | parsePTExt(block);
323 | break;
324 | case 0xFF:
325 | block.extType = 'app';
326 | parseAppExt(block);
327 | break;
328 | default:
329 | block.extType = 'unknown';
330 | parseUnknownExt(block);
331 | break;
332 | }
333 | };
334 |
335 | var parseImg = function (img) {
336 | var deinterlace = function (pixels, width) {
337 | // Of course this defeats the purpose of interlacing. And it's *probably*
338 | // the least efficient way it's ever been implemented. But nevertheless...
339 | var newPixels = new Array(pixels.length);
340 | var rows = pixels.length / width;
341 | var cpRow = function (toRow, fromRow) {
342 | var fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width);
343 | newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels));
344 | };
345 |
346 | // See appendix E.
347 | var offsets = [0, 4, 2, 1];
348 | var steps = [8, 8, 4, 2];
349 |
350 | var fromRow = 0;
351 | for (var pass = 0; pass < 4; pass++) {
352 | for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {
353 | cpRow(toRow, fromRow)
354 | fromRow++;
355 | }
356 | }
357 |
358 | return newPixels;
359 | };
360 |
361 | img.leftPos = st.readUnsigned();
362 | img.topPos = st.readUnsigned();
363 | img.width = st.readUnsigned();
364 | img.height = st.readUnsigned();
365 |
366 | var bits = byteToBitArr(st.readByte());
367 | img.lctFlag = bits.shift();
368 | img.interlaced = bits.shift();
369 | img.sorted = bits.shift();
370 | img.reserved = bits.splice(0, 2);
371 | img.lctSize = bitsToNum(bits.splice(0, 3));
372 |
373 | if (img.lctFlag) {
374 | img.lct = parseCT(1 << (img.lctSize + 1));
375 | }
376 |
377 | img.lzwMinCodeSize = st.readByte();
378 |
379 | var lzwData = readSubBlocks();
380 |
381 | img.pixels = lzwDecode(img.lzwMinCodeSize, lzwData);
382 |
383 | if (img.interlaced) { // Move
384 | img.pixels = deinterlace(img.pixels, img.width);
385 | }
386 |
387 | handler.img && handler.img(img);
388 | };
389 |
390 | var parseBlock = function () {
391 | var block = {};
392 | block.sentinel = st.readByte();
393 |
394 | switch (String.fromCharCode(block.sentinel)) { // For ease of matching
395 | case '!':
396 | block.type = 'ext';
397 | parseExt(block);
398 | break;
399 | case ',':
400 | block.type = 'img';
401 | parseImg(block);
402 | break;
403 | case ';':
404 | block.type = 'eof';
405 | handler.eof && handler.eof(block);
406 | break;
407 | default:
408 | throw new Error('Unknown block: 0x' + block.sentinel.toString(16)); // TODO: Pad this with a 0.
409 | }
410 |
411 | if (block.type !== 'eof') setTimeout(parseBlock, 0);
412 | };
413 |
414 | var parse = function () {
415 | parseHeader();
416 | setTimeout(parseBlock, 0);
417 | };
418 |
419 | parse();
420 | };
421 |
422 | var SuperGif = function ( opts ) {
423 | var options = {
424 | //viewport position
425 | vp_l: 0,
426 | vp_t: 0,
427 | vp_w: null,
428 | vp_h: null,
429 | //canvas sizes
430 | c_w: null,
431 | c_h: null
432 | };
433 | for (var i in opts ) { options[i] = opts[i] }
434 | if (options.vp_w && options.vp_h) options.is_vp = true;
435 |
436 | var stream;
437 | var hdr;
438 |
439 | var loadError = null;
440 | var loading = false;
441 |
442 | var transparency = null;
443 | var delay = null;
444 | var disposalMethod = null;
445 | var disposalRestoreFromIdx = null;
446 | var lastDisposalMethod = null;
447 | var frame = null;
448 | var lastImg = null;
449 |
450 | var playing = true;
451 | var forward = true;
452 |
453 | var ctx_scaled = false;
454 |
455 | var frames = [];
456 | var frameOffsets = []; // elements have .x and .y properties
457 |
458 | var gif = options.gif;
459 | if (typeof options.auto_play == 'undefined')
460 | options.auto_play = (!gif.getAttribute('rel:auto_play') || gif.getAttribute('rel:auto_play') == '1');
461 |
462 | var onEndListener = (options.hasOwnProperty('on_end') ? options.on_end : null);
463 | var loopDelay = (options.hasOwnProperty('loop_delay') ? options.loop_delay : 0);
464 | var overrideLoopMode = (options.hasOwnProperty('loop_mode') ? options.loop_mode : 'auto');
465 | var drawWhileLoading = (options.hasOwnProperty('draw_while_loading') ? options.draw_while_loading : true);
466 | var showProgressBar = drawWhileLoading ? (options.hasOwnProperty('show_progress_bar') ? options.show_progress_bar : true) : false;
467 | var progressBarHeight = (options.hasOwnProperty('progressbar_height') ? options.progressbar_height : 25);
468 | var progressBarBackgroundColor = (options.hasOwnProperty('progressbar_background_color') ? options.progressbar_background_color : 'rgba(255,255,255,0.4)');
469 | var progressBarForegroundColor = (options.hasOwnProperty('progressbar_foreground_color') ? options.progressbar_foreground_color : 'rgba(255,0,22,.8)');
470 |
471 | var clear = function () {
472 | transparency = null;
473 | delay = null;
474 | lastDisposalMethod = disposalMethod;
475 | disposalMethod = null;
476 | frame = null;
477 | };
478 |
479 | // XXX: There's probably a better way to handle catching exceptions when
480 | // callbacks are involved.
481 | var doParse = function () {
482 | try {
483 | parseGIF(stream, handler);
484 | }
485 | catch (err) {
486 | doLoadError('parse');
487 | }
488 | };
489 |
490 | var doText = function (text) {
491 | toolbar.innerHTML = text; // innerText? Escaping? Whatever.
492 | toolbar.style.visibility = 'visible';
493 | };
494 |
495 | var setSizes = function(w, h) {
496 | canvas.width = w * get_canvas_scale();
497 | canvas.height = h * get_canvas_scale();
498 | toolbar.style.minWidth = ( w * get_canvas_scale() ) + 'px';
499 |
500 | tmpCanvas.width = w;
501 | tmpCanvas.height = h;
502 | tmpCanvas.style.width = w + 'px';
503 | tmpCanvas.style.height = h + 'px';
504 | tmpCanvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0);
505 | };
506 |
507 | var setFrameOffset = function(frame, offset) {
508 | if (!frameOffsets[frame]) {
509 | frameOffsets[frame] = offset;
510 | return;
511 | }
512 | if (typeof offset.x !== 'undefined') {
513 | frameOffsets[frame].x = offset.x;
514 | }
515 | if (typeof offset.y !== 'undefined') {
516 | frameOffsets[frame].y = offset.y;
517 | }
518 | };
519 |
520 | var doShowProgress = function (pos, length, draw) {
521 | if (draw && showProgressBar) {
522 | var height = progressBarHeight;
523 | var left, mid, top, width;
524 | if (options.is_vp) {
525 | if (!ctx_scaled) {
526 | top = (options.vp_t + options.vp_h - height);
527 | height = height;
528 | left = options.vp_l;
529 | mid = left + (pos / length) * options.vp_w;
530 | width = canvas.width;
531 | } else {
532 | top = (options.vp_t + options.vp_h - height) / get_canvas_scale();
533 | height = height / get_canvas_scale();
534 | left = (options.vp_l / get_canvas_scale() );
535 | mid = left + (pos / length) * (options.vp_w / get_canvas_scale());
536 | width = canvas.width / get_canvas_scale();
537 | }
538 | //some debugging, draw rect around viewport
539 | if (false) {
540 | if (!ctx_scaled) {
541 | var l = options.vp_l, t = options.vp_t;
542 | var w = options.vp_w, h = options.vp_h;
543 | } else {
544 | var l = options.vp_l/get_canvas_scale(), t = options.vp_t/get_canvas_scale();
545 | var w = options.vp_w/get_canvas_scale(), h = options.vp_h/get_canvas_scale();
546 | }
547 | ctx.rect(l,t,w,h);
548 | ctx.stroke();
549 | }
550 | }
551 | else {
552 | top = (canvas.height - height) / (ctx_scaled ? get_canvas_scale() : 1);
553 | mid = ((pos / length) * canvas.width) / (ctx_scaled ? get_canvas_scale() : 1);
554 | width = canvas.width / (ctx_scaled ? get_canvas_scale() : 1 );
555 | height /= ctx_scaled ? get_canvas_scale() : 1;
556 | }
557 |
558 | ctx.fillStyle = progressBarBackgroundColor;
559 | ctx.fillRect(mid, top, width - mid, height);
560 |
561 | ctx.fillStyle = progressBarForegroundColor;
562 | ctx.fillRect(0, top, mid, height);
563 | }
564 | };
565 |
566 | var doLoadError = function (originOfError) {
567 | var drawError = function () {
568 | ctx.fillStyle = 'black';
569 | ctx.fillRect(0, 0, options.c_w ? options.c_w : hdr.width, options.c_h ? options.c_h : hdr.height);
570 | ctx.strokeStyle = 'red';
571 | ctx.lineWidth = 3;
572 | ctx.moveTo(0, 0);
573 | ctx.lineTo(options.c_w ? options.c_w : hdr.width, options.c_h ? options.c_h : hdr.height);
574 | ctx.moveTo(0, options.c_h ? options.c_h : hdr.height);
575 | ctx.lineTo(options.c_w ? options.c_w : hdr.width, 0);
576 | ctx.stroke();
577 | };
578 |
579 | loadError = originOfError;
580 | hdr = {
581 | width: gif.width,
582 | height: gif.height
583 | }; // Fake header.
584 | frames = [];
585 | drawError();
586 | };
587 |
588 | var doHdr = function (_hdr) {
589 | hdr = _hdr;
590 | setSizes(hdr.width, hdr.height)
591 | };
592 |
593 | var doGCE = function (gce) {
594 | pushFrame();
595 | clear();
596 | transparency = gce.transparencyGiven ? gce.transparencyIndex : null;
597 | delay = gce.delayTime;
598 | disposalMethod = gce.disposalMethod;
599 | // We don't have much to do with the rest of GCE.
600 | };
601 |
602 | var pushFrame = function () {
603 | if (!frame) return;
604 | frames.push({
605 | data: frame.getImageData(0, 0, hdr.width, hdr.height),
606 | delay: delay
607 | });
608 | frameOffsets.push({ x: 0, y: 0 });
609 | };
610 |
611 | var doImg = function (img) {
612 | if (!frame) frame = tmpCanvas.getContext('2d');
613 |
614 | var currIdx = frames.length;
615 |
616 | //ct = color table, gct = global color table
617 | var ct = img.lctFlag ? img.lct : hdr.gct; // TODO: What if neither exists?
618 |
619 | /*
620 | Disposal method indicates the way in which the graphic is to
621 | be treated after being displayed.
622 |
623 | Values : 0 - No disposal specified. The decoder is
624 | not required to take any action.
625 | 1 - Do not dispose. The graphic is to be left
626 | in place.
627 | 2 - Restore to background color. The area used by the
628 | graphic must be restored to the background color.
629 | 3 - Restore to previous. The decoder is required to
630 | restore the area overwritten by the graphic with
631 | what was there prior to rendering the graphic.
632 |
633 | Importantly, "previous" means the frame state
634 | after the last disposal of method 0, 1, or 2.
635 | */
636 | if (currIdx > 0) {
637 | if (lastDisposalMethod === 3) {
638 | // Restore to previous
639 | // If we disposed every frame including first frame up to this point, then we have
640 | // no composited frame to restore to. In this case, restore to background instead.
641 | if (disposalRestoreFromIdx !== null) {
642 | frame.putImageData(frames[disposalRestoreFromIdx].data, 0, 0);
643 | } else {
644 | frame.clearRect(lastImg.leftPos, lastImg.topPos, lastImg.width, lastImg.height);
645 | }
646 | } else {
647 | disposalRestoreFromIdx = currIdx - 1;
648 | }
649 |
650 | if (lastDisposalMethod === 2) {
651 | // Restore to background color
652 | // Browser implementations historically restore to transparent; we do the same.
653 | // http://www.wizards-toolkit.org/discourse-server/viewtopic.php?f=1&t=21172#p86079
654 | frame.clearRect(lastImg.leftPos, lastImg.topPos, lastImg.width, lastImg.height);
655 | }
656 | }
657 | // else, Undefined/Do not dispose.
658 | // frame contains final pixel data from the last frame; do nothing
659 |
660 | //Get existing pixels for img region after applying disposal method
661 | var imgData = frame.getImageData(img.leftPos, img.topPos, img.width, img.height);
662 |
663 | //apply color table colors
664 | img.pixels.forEach(function (pixel, i) {
665 | // imgData.data === [R,G,B,A,R,G,B,A,...]
666 | if (pixel !== transparency) {
667 | imgData.data[i * 4 + 0] = ct[pixel][0];
668 | imgData.data[i * 4 + 1] = ct[pixel][1];
669 | imgData.data[i * 4 + 2] = ct[pixel][2];
670 | imgData.data[i * 4 + 3] = 255; // Opaque.
671 | }
672 | });
673 |
674 | frame.putImageData(imgData, img.leftPos, img.topPos);
675 |
676 | if (!ctx_scaled) {
677 | ctx.scale(get_canvas_scale(),get_canvas_scale());
678 | ctx_scaled = true;
679 | }
680 |
681 | // We could use the on-page canvas directly, except that we draw a progress
682 | // bar for each image chunk (not just the final image).
683 | if (drawWhileLoading) {
684 | ctx.drawImage(tmpCanvas, 0, 0);
685 | drawWhileLoading = options.auto_play;
686 | }
687 |
688 | lastImg = img;
689 | };
690 |
691 | var player = (function () {
692 | var i = -1;
693 | var iterationCount = 0;
694 |
695 | var showingInfo = false;
696 | var pinned = false;
697 |
698 | /**
699 | * Gets the index of the frame "up next".
700 | * @returns {number}
701 | */
702 | var getNextFrameNo = function () {
703 | var delta = (forward ? 1 : -1);
704 | return (i + delta + frames.length) % frames.length;
705 | };
706 |
707 | var stepFrame = function (amount) { // XXX: Name is confusing.
708 | i = i + amount;
709 |
710 | putFrame();
711 | };
712 |
713 | var step = (function () {
714 | var stepping = false;
715 |
716 | var completeLoop = function () {
717 | if (onEndListener !== null)
718 | onEndListener(gif);
719 | iterationCount++;
720 |
721 | if (overrideLoopMode !== false || iterationCount < 0) {
722 | doStep();
723 | } else {
724 | stepping = false;
725 | playing = false;
726 | }
727 | };
728 |
729 | var doStep = function () {
730 | stepping = playing;
731 | if (!stepping) return;
732 |
733 | stepFrame(1);
734 | var delay = frames[i].delay * 10;
735 | if (!delay) delay = 100; // FIXME: Should this even default at all? What should it be?
736 |
737 | var nextFrameNo = getNextFrameNo();
738 | if (nextFrameNo === 0) {
739 | delay += loopDelay;
740 | setTimeout(completeLoop, delay);
741 | } else {
742 | setTimeout(doStep, delay);
743 | }
744 | };
745 |
746 | return function () {
747 | if (!stepping) setTimeout(doStep, 0);
748 | };
749 | }());
750 |
751 | var putFrame = function () {
752 | var offset;
753 | i = parseInt(i, 10);
754 |
755 | if (i > frames.length - 1){
756 | i = 0;
757 | }
758 |
759 | if (i < 0){
760 | i = 0;
761 | }
762 |
763 | offset = frameOffsets[i];
764 |
765 | tmpCanvas.getContext("2d").putImageData(frames[i].data, offset.x, offset.y);
766 | ctx.globalCompositeOperation = "copy";
767 | ctx.drawImage(tmpCanvas, 0, 0);
768 | };
769 |
770 | var play = function () {
771 | playing = true;
772 | step();
773 | };
774 |
775 | var pause = function () {
776 | playing = false;
777 | };
778 |
779 |
780 | return {
781 | init: function () {
782 | if (loadError) return;
783 |
784 | if ( ! (options.c_w && options.c_h) ) {
785 | ctx.scale(get_canvas_scale(),get_canvas_scale());
786 | }
787 |
788 | if (options.auto_play) {
789 | step();
790 | }
791 | else {
792 | i = 0;
793 | putFrame();
794 | }
795 | },
796 | step: step,
797 | play: play,
798 | pause: pause,
799 | playing: playing,
800 | move_relative: stepFrame,
801 | current_frame: function() { return i; },
802 | length: function() { return frames.length },
803 | move_to: function ( frame_idx ) {
804 | i = frame_idx;
805 | putFrame();
806 | }
807 | }
808 | }());
809 |
810 | var doDecodeProgress = function (draw) {
811 | doShowProgress(stream.pos, stream.data.length, draw);
812 | };
813 |
814 | var doNothing = function () {};
815 | /**
816 | * @param{boolean=} draw Whether to draw progress bar or not; this is not idempotent because of translucency.
817 | * Note that this means that the text will be unsynchronized with the progress bar on non-frames;
818 | * but those are typically so small (GCE etc.) that it doesn't really matter. TODO: Do this properly.
819 | */
820 | var withProgress = function (fn, draw) {
821 | return function (block) {
822 | fn(block);
823 | doDecodeProgress(draw);
824 | };
825 | };
826 |
827 |
828 | var handler = {
829 | hdr: withProgress(doHdr),
830 | gce: withProgress(doGCE),
831 | com: withProgress(doNothing),
832 | // I guess that's all for now.
833 | app: {
834 | // TODO: Is there much point in actually supporting iterations?
835 | NETSCAPE: withProgress(doNothing)
836 | },
837 | img: withProgress(doImg, true),
838 | eof: function (block) {
839 | //toolbar.style.display = '';
840 | pushFrame();
841 | doDecodeProgress(false);
842 | if ( ! (options.c_w && options.c_h) ) {
843 | canvas.width = hdr.width * get_canvas_scale();
844 | canvas.height = hdr.height * get_canvas_scale();
845 | }
846 | player.init();
847 | loading = false;
848 | if (load_callback) {
849 | load_callback(gif);
850 | }
851 |
852 | }
853 | };
854 |
855 | var init = function () {
856 | var parent = gif.parentNode;
857 |
858 | var div = document.createElement('div');
859 | canvas = document.createElement('canvas');
860 | ctx = canvas.getContext('2d');
861 | toolbar = document.createElement('div');
862 |
863 | tmpCanvas = document.createElement('canvas');
864 |
865 | div.width = canvas.width = gif.width;
866 | div.height = canvas.height = gif.height;
867 | toolbar.style.minWidth = gif.width + 'px';
868 |
869 | div.className = 'jsgif';
870 | toolbar.className = 'jsgif_toolbar';
871 | div.appendChild(canvas);
872 | div.appendChild(toolbar);
873 |
874 | parent.insertBefore(div, gif);
875 | parent.removeChild(gif);
876 |
877 | if (options.c_w && options.c_h) setSizes(options.c_w, options.c_h);
878 | initialized=true;
879 | };
880 |
881 | var get_canvas_scale = function() {
882 | var scale;
883 | if (options.max_width && hdr && hdr.width > options.max_width) {
884 | scale = options.max_width / hdr.width;
885 | }
886 | else {
887 | scale = 1;
888 | }
889 | return scale;
890 | }
891 |
892 | var canvas, ctx, toolbar, tmpCanvas;
893 | var initialized = false;
894 | var load_callback = false;
895 |
896 | var load_setup = function(callback) {
897 | if (loading) return false;
898 | if (callback) load_callback = callback;
899 | else load_callback = false;
900 |
901 | loading = true;
902 | frames = [];
903 | clear();
904 | disposalRestoreFromIdx = null;
905 | lastDisposalMethod = null;
906 | frame = null;
907 | lastImg = null;
908 |
909 | return true;
910 | }
911 |
912 | return {
913 | // play controls
914 | play: player.play,
915 | pause: player.pause,
916 | move_relative: player.move_relative,
917 | move_to: player.move_to,
918 |
919 | // getters for instance vars
920 | get_playing : function() { return playing },
921 | get_canvas : function() { return canvas },
922 | get_canvas_scale : function() { return get_canvas_scale() },
923 | get_loading : function() { return loading },
924 | get_auto_play : function() { return options.auto_play },
925 | get_length : function() { return player.length() },
926 | get_current_frame: function() { return player.current_frame() },
927 | load_url: function(src,callback){
928 | if (!load_setup(callback)) return;
929 |
930 | var h = new XMLHttpRequest();
931 | // new browsers (XMLHttpRequest2-compliant)
932 | h.open('GET', src, true);
933 |
934 | if ('overrideMimeType' in h) {
935 | h.overrideMimeType('text/plain; charset=x-user-defined');
936 | }
937 |
938 | // old browsers (XMLHttpRequest-compliant)
939 | else if ('responseType' in h) {
940 | h.responseType = 'arraybuffer';
941 | }
942 |
943 | // IE9 (Microsoft.XMLHTTP-compliant)
944 | else {
945 | h.setRequestHeader('Accept-Charset', 'x-user-defined');
946 | }
947 |
948 | h.onloadstart = function() {
949 | // Wait until connection is opened to replace the gif element with a canvas to avoid a blank img
950 | if (!initialized) init();
951 | };
952 | h.onload = function(e) {
953 | if (this.status != 200) {
954 | doLoadError('xhr - response');
955 | }
956 | // emulating response field for IE9
957 | if (!('response' in this)) {
958 | this.response = new VBArray(this.responseText).toArray().map(String.fromCharCode).join('');
959 | }
960 | var data = this.response;
961 | if (data.toString().indexOf("ArrayBuffer") > 0) {
962 | data = new Uint8Array(data);
963 | }
964 |
965 | stream = new Stream(data);
966 | setTimeout(doParse, 0);
967 | };
968 | h.onprogress = function (e) {
969 | if (e.lengthComputable) doShowProgress(e.loaded, e.total, true);
970 | };
971 | h.onerror = function() { doLoadError('xhr'); };
972 | h.send();
973 | },
974 | load: function (callback) {
975 | this.load_url(gif.getAttribute('rel:animated_src') || gif.src,callback);
976 | },
977 | load_raw: function(arr, callback) {
978 | if (!load_setup(callback)) return;
979 | if (!initialized) init();
980 | stream = new Stream(arr);
981 | setTimeout(doParse, 0);
982 | },
983 | set_frame_offset: setFrameOffset
984 | };
985 | };
986 |
987 | return SuperGif;
988 | }));
989 |
990 |
991 |
--------------------------------------------------------------------------------
/js/require.js:
--------------------------------------------------------------------------------
1 | /*
2 | RequireJS 2.2.0 Copyright jQuery Foundation and other contributors.
3 | Released under MIT license, http://github.com/requirejs/requirejs/LICENSE
4 | */
5 | var requirejs,require,define;
6 | (function(ga){function ka(b,c,d,g){return g||""}function K(b){return"[object Function]"===Q.call(b)}function L(b){return"[object Array]"===Q.call(b)}function y(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(K(k)){if(this.events.error&&this.map.isDefine||g.onError!==
18 | ha)try{h=l.execCb(c,k,b,h)}catch(d){a=d}else h=l.execCb(c,k,b,h);this.map.isDefine&&void 0===h&&((b=this.module)?h=b.exports:this.usingExports&&(h=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",A(this.error=a)}else h=k;this.exports=h;if(this.map.isDefine&&!this.ignore&&(v[c]=h,g.onResourceLoad)){var f=[];y(this.depMaps,function(a){f.push(a.normalizedMap||a)});g.onResourceLoad(l,this.map,f)}C(c);
19 | this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}},callPlugin:function(){var a=this.map,b=a.id,d=q(a.prefix);this.depMaps.push(d);w(d,"defined",z(this,function(h){var k,f,d=e(fa,this.map.id),M=this.map.name,r=this.map.parentMap?this.map.parentMap.name:null,m=l.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(h.normalize&&(M=h.normalize(M,function(a){return c(a,r,!0)})||
20 | ""),f=q(a.prefix+"!"+M,this.map.parentMap),w(f,"defined",z(this,function(a){this.map.normalizedMap=f;this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),h=e(t,f.id)){this.depMaps.push(f);if(this.events.error)h.on("error",z(this,function(a){this.emit("error",a)}));h.enable()}}else d?(this.map.url=l.nameToUrl(d),this.load()):(k=z(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),k.error=z(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];D(t,function(a){0===
21 | a.map.id.indexOf(b+"_unnormalized")&&C(a.map.id)});A(a)}),k.fromText=z(this,function(h,c){var d=a.name,f=q(d),M=S;c&&(h=c);M&&(S=!1);u(f);x(p.config,b)&&(p.config[d]=p.config[b]);try{g.exec(h)}catch(e){return A(F("fromtexteval","fromText eval for "+b+" failed: "+e,e,[b]))}M&&(S=!0);this.depMaps.push(f);l.completeLoad(d);m([d],k)}),h.load(a.name,m,k,p))}));l.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){Z[this.map.id]=this;this.enabling=this.enabled=!0;y(this.depMaps,z(this,function(a,
22 | b){var c,h;if("string"===typeof a){a=q(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=e(R,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;w(a,"defined",z(this,function(a){this.undefed||(this.defineDep(b,a),this.check())}));this.errback?w(a,"error",z(this,this.errback)):this.events.error&&w(a,"error",z(this,function(a){this.emit("error",a)}))}c=a.id;h=t[c];x(R,c)||!h||h.enabled||l.enable(a,this)}));D(this.pluginMaps,z(this,function(a){var b=e(t,a.id);
23 | b&&!b.enabled&&l.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};l={config:p,contextName:b,registry:t,defined:v,urlFetched:W,defQueue:G,defQueueMap:{},Module:da,makeModuleMap:q,nextTick:g.nextTick,onError:A,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");if("string"===typeof a.urlArgs){var b=
24 | a.urlArgs;a.urlArgs=function(a,c){return(-1===c.indexOf("?")?"?":"&")+b}}var c=p.shim,h={paths:!0,bundles:!0,config:!0,map:!0};D(a,function(a,b){h[b]?(p[b]||(p[b]={}),Y(p[b],a,!0,!0)):p[b]=a});a.bundles&&D(a.bundles,function(a,b){y(a,function(a){a!==b&&(fa[a]=b)})});a.shim&&(D(a.shim,function(a,b){L(a)&&(a={deps:a});!a.exports&&!a.init||a.exportsFn||(a.exportsFn=l.makeShimExports(a));c[b]=a}),p.shim=c);a.packages&&y(a.packages,function(a){var b;a="string"===typeof a?{name:a}:a;b=a.name;a.location&&
25 | (p.paths[b]=a.location);p.pkgs[b]=a.name+"/"+(a.main||"main").replace(na,"").replace(U,"")});D(t,function(a,b){a.inited||a.map.unnormalized||(a.map=q(b,null,!0))});(a.deps||a.callback)&&l.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ga,arguments));return b||a.exports&&ia(a.exports)}},makeRequire:function(a,n){function m(c,d,f){var e,r;n.enableBuildCallback&&d&&K(d)&&(d.__requireJsBuild=!0);if("string"===typeof c){if(K(d))return A(F("requireargs",
26 | "Invalid require call"),f);if(a&&x(R,c))return R[c](t[a.id]);if(g.get)return g.get(l,c,a,m);e=q(c,a,!1,!0);e=e.id;return x(v,e)?v[e]:A(F("notloaded",'Module name "'+e+'" has not been loaded yet for context: '+b+(a?"":". Use require([])")))}P();l.nextTick(function(){P();r=u(q(null,a));r.skipMap=n.skipMap;r.init(c,d,f,{enabled:!0});H()});return m}n=n||{};Y(m,{isBrowser:E,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];-1!==f&&("."!==g&&".."!==g||1e.attachEvent.toString().indexOf("[native code")||ca?(e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)):(S=!0,e.attachEvent("onreadystatechange",b.onScriptLoad));e.src=d;if(m.onNodeCreated)m.onNodeCreated(e,m,c,d);P=e;H?C.insertBefore(e,H):C.appendChild(e);P=null;return e}if(ja)try{setTimeout(function(){},
35 | 0),importScripts(d),b.completeLoad(c)}catch(q){b.onError(F("importscripts","importScripts failed for "+c+" at "+d,q,[c]))}};E&&!w.skipDataMain&&X(document.getElementsByTagName("script"),function(b){C||(C=b.parentNode);if(O=b.getAttribute("data-main"))return u=O,w.baseUrl||-1!==u.indexOf("!")||(I=u.split("/"),u=I.pop(),T=I.length?I.join("/")+"/":"./",w.baseUrl=T),u=u.replace(U,""),g.jsExtRegExp.test(u)&&(u=O),w.deps=w.deps?w.deps.concat(u):[u],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&
36 | (d=c,c=b,b=null);L(c)||(d=c,c=null);!c&&K(d)&&(c=[],d.length&&(d.toString().replace(qa,ka).replace(ra,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));S&&(e=P||pa())&&(b||(b=e.getAttribute("data-requiremodule")),g=J[e.getAttribute("data-requirecontext")]);g?(g.defQueue.push([b,c,d]),g.defQueueMap[b]=!0):V.push([b,c,d])};define.amd={jQuery:!0};g.exec=function(b){return eval(b)};g(w)}})(this);
37 |
--------------------------------------------------------------------------------
/js/rubbable.js:
--------------------------------------------------------------------------------
1 | /*
2 | RubbableGif
3 |
4 | Example usage:
5 |
6 |
7 |
8 |
16 |
17 | Image tag attributes:
18 |
19 | rel:animated_src - If this url is specified, it's loaded into the player instead of src.
20 | This allows a preview frame to be shown until animated gif data is streamed into the canvas
21 |
22 | rel:auto_play - Defaults to 1 if not specified. If set to zero, the gif will be rubbable but will not
23 | animate unless the user is rubbing it.
24 |
25 | Constructor options args
26 |
27 | gif Required. The DOM element of an img tag.
28 | auto_play Optional. Same as the rel:auto_play attribute above, this arg overrides the img tag info.
29 | max_width Optional. Scale images over max_width down to max_width. Helpful with mobile.
30 |
31 | Instance methods
32 |
33 | // loading
34 | load( callback ) Loads the gif into a canvas element and then calls callback if one is passed
35 |
36 | // play controls
37 | play - Start playing the gif
38 | pause - Stop playing the gif
39 | move_to(i) - Move to frame i of the gif
40 | move_relative(i) - Move i frames ahead (or behind if i < 0)
41 |
42 | // getters
43 | get_canvas The canvas element that the gif is playing in.
44 | get_playing Whether or not the gif is currently playing
45 | get_loading Whether or not the gif has finished loading/parsing
46 | get_auto_play Whether or not the gif is set to play automatically
47 | get_length The number of frames in the gif
48 | get_current_frame The index of the currently displayed frame of the gif
49 |
50 | For additional customization (viewport inside iframe) these params may be passed:
51 | c_w, c_h - width and height of canvas
52 | vp_t, vp_l, vp_ w, vp_h - top, left, width and height of the viewport
53 |
54 | */
55 | (function (root, factory) {
56 | if (typeof define === 'function' && define.amd) {
57 | define(['./libgif'], factory);
58 | } else if (typeof exports === 'object') {
59 | module.exports = factory(require('./libgif'));
60 | } else {
61 | root.RubbableGif = factory(root.SuperGif);
62 | }
63 | }(this, function (SuperGif) {
64 | var RubbableGif = function( options ) {
65 | var sup = new SuperGif( options );
66 |
67 | var register_canvas_handers = function () {
68 |
69 | var isvp = function(x) {
70 | return (options.vp_l ? ( x - options.vp_l ) : x );
71 | }
72 |
73 | var canvas = sup.get_canvas();
74 | var maxTime = 1000,
75 | // allow movement if < 1000 ms (1 sec)
76 | w = ( options.vp_w ? options.vp_w : canvas.width ),
77 | maxDistance = Math.floor(w / (sup.get_length() * 2)),
78 | // swipe movement of 50 pixels triggers the swipe
79 | startX = 0,
80 | startTime = 0;
81 |
82 | var cantouch = "ontouchend" in document;
83 |
84 | var aj = 0;
85 | var last_played = 0;
86 |
87 | canvas.addEventListener((cantouch) ? 'touchstart' : 'mousedown', function (e) {
88 | // prevent image drag (Firefox)
89 | e.preventDefault();
90 | if (sup.get_auto_play()) sup.pause();
91 |
92 | var pos = (e.touches && e.touches.length > 0) ? e.touches[0] : e;
93 |
94 | var x = (pos.layerX > 0) ? isvp(pos.layerX) : w / 2;
95 | var progress = x / w;
96 |
97 | sup.move_to( Math.floor(progress * (sup.get_length() - 1)) );
98 |
99 | startTime = e.timeStamp;
100 | startX = isvp(pos.pageX);
101 | });
102 |
103 | canvas.addEventListener((cantouch) ? 'touchend' : 'mouseup', function (e) {
104 | startTime = 0;
105 | startX = 0;
106 | if (sup.get_auto_play()) sup.play();
107 | });
108 |
109 | canvas.addEventListener((cantouch) ? 'touchmove' : 'mousemove', function (e) {
110 | e.preventDefault();
111 | var pos = (e.touches && e.touches.length > 0) ? e.touches[0] : e;
112 |
113 | var currentX = isvp(pos.pageX);
114 | currentDistance = (startX === 0) ? 0 : Math.abs(currentX - startX);
115 | // allow if movement < 1 sec
116 | currentTime = e.timeStamp;
117 | if (startTime !== 0 && currentDistance > maxDistance) {
118 | if (currentX < startX && sup.get_current_frame() > 0) {
119 | sup.move_relative(-1);
120 | }
121 | if (currentX > startX && sup.get_current_frame() < sup.get_length() - 1) {
122 | sup.move_relative(1);
123 | }
124 | startTime = e.timeStamp;
125 | startX = isvp(pos.pageX);
126 | }
127 |
128 | var time_since_last_play = e.timeStamp - last_played;
129 | {
130 | aj++;
131 | if (document.getElementById('tickles' + ((aj % 5) + 1))) document.getElementById('tickles' + ((aj % 5) + 1)).play();
132 | last_played = e.timeStamp;
133 | }
134 |
135 |
136 | });
137 | };
138 |
139 | sup.orig_load = sup.load;
140 | sup.load = function(callback) {
141 | sup.orig_load( function() {
142 | if (callback) callback();
143 | register_canvas_handers( sup );
144 | } );
145 | }
146 |
147 | return sup;
148 | }
149 |
150 | return RubbableGif;
151 | }));
--------------------------------------------------------------------------------
/notebooks/gps_utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import math
4 | import numpy as np
5 |
6 | # RDP algorithm as long as the rdp package is not iterative.
7 | # See https://github.com/fhirschmann/rdp/issues/5
8 | def _DouglasPeucker(points, startIndex, lastIndex, epsilon):
9 | stk = []
10 | stk.append([startIndex, lastIndex])
11 | globalStartIndex = startIndex
12 | bitArray = np.ones(lastIndex-startIndex+1, dtype=bool)
13 |
14 | while len(stk) > 0:
15 | startIndex = stk[-1][0]
16 | lastIndex = stk[-1][1]
17 | stk.pop()
18 |
19 | dmax = 0.
20 | index = startIndex
21 |
22 | for i in range(index+1, lastIndex):
23 | if bitArray[i - globalStartIndex]:
24 | d = PointLineDistance(points[i], points[startIndex], points[lastIndex])
25 | if d > dmax:
26 | index = i
27 | dmax = d
28 | if dmax > epsilon:
29 | stk.append([startIndex, index])
30 | stk.append([index, lastIndex])
31 | else:
32 | for i in range(startIndex + 1, lastIndex):
33 | bitArray[i - globalStartIndex] = False
34 | return bitArray
35 |
36 |
37 | def rdp(points, epsilon):
38 | """
39 | Ramer-Douglas-Peucker algorithm
40 | """
41 | bitArray = _DouglasPeucker(points, 0, len(points)-1, epsilon)
42 | resList = []
43 | for i in range(len(points)):
44 | if bitArray[i]:
45 | resList.append(points[i])
46 | return np.array(resList)
47 |
48 |
49 | def PointLineDistance(point, start, end):
50 | if np.all(np.equal(start, end)) :
51 | return np.linalg.norm(point, start)
52 | n = abs((end[0] - start[0]) * (start[1] - point[1]) - (start[0] - point[0]) * (end[1] - start[1]))
53 | d = math.sqrt((end[0] - start[0]) * (end[0] - start[0]) + (end[1] - start[1]) * (end[1] - start[1]))
54 | return n/d
55 |
56 |
57 | def haversine(coord1, coord2):
58 | """
59 | Haversine distance in meters for two (lat, lon) coordinates
60 | """
61 | lat1, lon1 = coord1
62 | lat2, lon2 = coord2
63 | radius = 6371000 # mean earth radius in meters (GRS 80-Ellipsoid)
64 | dlat = math.radians(lat2-lat1)
65 | dlon = math.radians(lon2-lon1)
66 | a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \
67 | * math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2)
68 | c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
69 | d = radius * c
70 | return d
71 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # Libraries needed for running the notebooks
2 |
3 | # Basics
4 | numpy
5 | scipy
6 | pandas
7 | matplotlib
8 | seaborn
9 | jupyter
10 |
11 | # GPS related
12 | gpxpy
13 | mplleaflet
14 | srtm.py
15 | pykalman
16 | rdp
17 |
--------------------------------------------------------------------------------
/show_slides.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | jupyter nbconvert talk.ipynb --to slides --post serve
3 |
--------------------------------------------------------------------------------