├── .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 | [![Binder](http://mybinder.org/badge.svg)](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 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 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 | --------------------------------------------------------------------------------