├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── deploy.sh ├── examples └── index.html ├── package.json ├── page ├── assets │ ├── css │ │ └── style.css │ └── js │ │ └── script.js └── index.html └── src ├── EventEmitter.js ├── GSVPano.js └── Pano.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | docs/ 3 | build/ 4 | gh-pages/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tests/ 2 | page/ 3 | examples/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | env: 5 | global: 6 | - GH_REF: github.com/juampi92/GSVPano.git 7 | - secure: "ghnO4pt9ewFtOdhwMHi1x9Ol5p3XBfBl7DYtJs4xMLKFq6e7toJZKClWZ+3mBGqqZz3jlw6xeq4PbGkQPczc/P1LkwykajzOFKWGnYi68RdWr20HAcSULGpKilMHN5JH3xJkYzsYKBDuH4P43R0rbY8b3KGxtQwArhJAox1CkEeM/M2SiFXgDrfy2ZBr51P7gG3gdW+3bzZsl7Ntg/l5HQT2nY68XOoGSkPR/STo7iLAmIm/dyym0/Ao3gPSQBLhSp88PkjqPlvALdTDibvFDC6TXzTM0fIvFeefu0ZWmaE2TYhMA9QolCZiy4CJiYXtg82dUmmXBsJQjHMVq+8OeW2fcztaui/PepBHGkeecpTj1QzUUvtkRFf9A7+erbzdWV9bQQ81mVnQ045Rri87DezO99YwbTrgjoQeFYojTXXvcKFPugMxx1aQPmzyBHqmRNpBOZOlsOESfqVEAF3nlgK89mB+m3eURNecr2aHJ0qbElBYSmq1jSoC5xnKJoX+TM2uOZJg42+K9pHjIBZVk2rUSgvJ2pt2X54qUZgg4U8vvIeeDTD416gyPli9ZOkNmBJ3tfzhJjIC+PDYA8tSvbL54+piGqPp2hG2/tOn1FZDg0aFkBbIUq7tQWhGMj9v0kaFPTX5muYu91Djy+JzpMyswuROlm1JCMvT+l+dsTk=" 8 | before_script: 9 | - npm install grunt-cli -g 10 | script: bash ./deploy.sh 11 | branches: 12 | only: 13 | - master -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Load modules 4 | grunt.loadNpmTasks('grunt-contrib-uglify'); 5 | grunt.loadNpmTasks('grunt-contrib-jshint'); 6 | grunt.loadNpmTasks('grunt-contrib-yuidoc'); 7 | grunt.loadNpmTasks('grunt-contrib-copy'); 8 | grunt.loadNpmTasks('grunt-contrib-clean'); 9 | grunt.loadNpmTasks('grunt-browserify'); 10 | grunt.loadNpmTasks('grunt-watchify'); 11 | 12 | // Configs 13 | grunt.initConfig({ 14 | pkg: grunt.file.readJSON('package.json'), 15 | 16 | uglify: { 17 | options: { 18 | banner: '/*!\n * <%= pkg.pkgname %> v<%= pkg.version %>\n * (c) <%= pkg.homepage %>\n * License: <%= pkg.license %>\n*/\n' 19 | }, 20 | build: { 21 | src: 'build/<%= pkg.pkgname %>.js', 22 | dest: 'build/<%= pkg.pkgname %>.min.js' 23 | } 24 | }, 25 | 26 | jshint: { 27 | options: { 28 | curly: false, // 29 | eqeqeq: false, // 30 | eqnull: true, 31 | browser: true, 32 | globals: {} 33 | }, 34 | all: ['src/**/*.js'] 35 | }, 36 | 37 | browserify: { 38 | dist: { 39 | files: { 40 | 'build/<%= pkg.pkgname %>.js': 'src/<%= pkg.pkgname %>.js' 41 | } 42 | } 43 | }, 44 | 45 | watchify: { 46 | options: { 47 | // defaults options used in b.bundle(opts) 48 | detectGlobals: true, 49 | insertGlobals: false, 50 | ignoreMissing: false, 51 | debug: true, 52 | standalone: false, 53 | keepalive: true 54 | }, 55 | dev: { 56 | src: './src/<%= pkg.pkgname %>.js', 57 | dest: 'build/<%= pkg.pkgname %>.js' 58 | } 59 | }, 60 | 61 | copy: { 62 | buildversion: { 63 | files: [{ 64 | src: 'build/<%= pkg.pkgname %>.js', 65 | dest: 'build/<%= pkg.pkgname %>-<%= pkg.version %>.js' 66 | }, { 67 | src: 'build/<%= pkg.pkgname %>.min.js', 68 | dest: 'build/<%= pkg.pkgname %>-<%= pkg.version %>.min.js' 69 | }, ] 70 | }, 71 | 72 | ghpages: { 73 | files: [{ 74 | expand: true, 75 | src: 'build/**/*', 76 | dest: 'gh-pages/' 77 | }, { 78 | expand: true, 79 | src: 'docs/**/*', 80 | dest: 'gh-pages/' 81 | }, { 82 | expand: true, 83 | src: 'examples/**/*', 84 | dest: 'gh-pages/' 85 | }, { 86 | expand: true, 87 | cwd: 'page/', 88 | src: '**/*', // page/assets/* 89 | dest: 'gh-pages/' 90 | }] 91 | }, 92 | }, 93 | 94 | clean: { 95 | build: ['build', 'docs'], 96 | ghpages: ['gh-pages/docs', 'gh-pages/assets', 'gh-pages/examples'] 97 | }, 98 | 99 | yuidoc: { 100 | compile: { 101 | name: '<%= pkg.pkgname %>', 102 | description: '<%= pkg.description %>', 103 | version: '<%= pkg.version %>', 104 | url: '<%= pkg.homepage %>', 105 | options: { 106 | paths: 'src/', 107 | outdir: 'docs/', 108 | themedir: 'node_modules/yuidoc-bootstrap-theme', 109 | helpers: ['node_modules/yuidoc-bootstrap-theme/helpers/helpers.js'] 110 | } 111 | } 112 | }, 113 | 114 | }); 115 | 116 | // Register tasks 117 | 118 | grunt.registerTask('build', [ 119 | 'jshint:all', 120 | 'clean:build', 121 | 'browserify', 122 | 'uglify', 123 | 'yuidoc:compile', 124 | 'copy:buildversion' 125 | ]); 126 | 127 | grunt.registerTask('gh-pages', [ 128 | 'clean:ghpages', 129 | 'copy:ghpages' 130 | ]); 131 | 132 | grunt.registerTask('watch', ['watchify:dev']); 133 | 134 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GSVPano - Google Street View Panorama lib 2 | [![Build Status](https://travis-ci.org/juampi92/GSVPano.svg?branch=master)](https://travis-ci.org/juampi92/GSVPano) 3 | 4 | Library to help requesting and stitching Google Street View panoramas. 5 | 6 | Given a Google Maps Location, this library downloads each part of the corresponding panorama, and gives you the complete image ready to use. 7 | 8 | ## Simple Example 9 | 10 | ```js 11 | // Create a PanoLoader object 12 | var loader = new GSVPANO.PanoLoader({ 13 | zoom: 3 14 | }); 15 | 16 | // Implement the onPanoramaLoad handler 17 | loader.on('panorama.load', function(panorama) { 18 | // panorama will now be loaded 19 | 20 | // for individual progress 21 | panorama.on('progress', changeProgress); 22 | 23 | // for individual complete callback 24 | panorama.on('complete', completeCallback); 25 | }); 26 | 27 | // Invoke the load method with a LatLng point 28 | loader.load( new google.maps.LatLng( 42.216188,-75.726578 ) ); 29 | ``` 30 | 31 | ## Installation 32 | 33 | ##### Dependencies: 34 | 35 | Remember that you'll need google.maps API, so don't forget this script tag. 36 | 37 | ```html 38 | 39 | ```` 40 | 41 | ##### Github CDN 42 | 43 | ```html 44 | 45 | ```` 46 | 47 | Or you can just use it with a specific version: 48 | 49 | ```html 50 | 51 | ```` 52 | 53 | ##### Bower 54 | 55 | $ bower install GSVPano 56 | 57 | ##### npm 58 | 59 | $ npm install gsvpano 60 | 61 | And you can require the source files and compile it using browserify 62 | 63 | ## API Reference 64 | 65 | ##### Events 66 | 67 | * `panorama.data ( pano )` When a new panorama has been added and its about to start downloading 68 | * `panorama.nodata ( location, status )` When the location had trouble fetching the panorama 69 | * `panorama.progress ( p , pano )` Shows the panorama load progress 70 | * `panorama.load ( pano )` When the panorama has finished loading 71 | * `error ( message )` When the panorama has finished loading 72 | 73 | ```js 74 | loader.on('panorama.data', function(pano) { 75 | console.log('Panorama ' + pano.id + ' has been added'); 76 | }); 77 | ``` 78 | 79 | ##### Methods 80 | * `on ( eventName, callback )` EventEmitter inheritance 81 | * `load ( location )` Starts the load of the panorama on that location 82 | * `setZoom ( z )` Sets the zoom 83 | 84 | ##### Read the [Documentation](https://juampi92.github.io/GSVPano/docs/) for more detail 85 | 86 | ## Contribute 87 | 88 | First, do 89 | 90 | $ npm install 91 | $ npm install -g grunt-cli 92 | 93 | to get all the dependencies. 94 | 95 | $ grunt build 96 | 97 | lints the project, browserifies it, minifies it and builds the documentation also. 98 | 99 | If you just wanna build it with browserify, use 100 | 101 | $ grunt watch 102 | 103 | that will watchify the `src/` folder. 104 | 105 | 106 | ##### Forks, pull requests and code critiques are welcome! 107 | 108 | ## License 109 | 110 | MIT License (MIT) -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GSVPano", 3 | "version": "1.0.0", 4 | "homepage": "https://juampi92.github.io/GSVPano/", 5 | "authors": [ 6 | "konforti", 7 | "Juan Pablo Barreto " 8 | ], 9 | "description": "Google Street View Panorama Util", 10 | "main": "build/GSVPano.min.js", 11 | "moduleType": [ 12 | "globals", 13 | "node" 14 | ], 15 | "keywords": [ 16 | "Google", 17 | "Street", 18 | "View", 19 | "Panorama" 20 | ], 21 | "license": "MIT", 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/juampi92/GSVPano.git" 25 | }, 26 | "ignore": [ 27 | "**/.*", 28 | "node_modules", 29 | "bower_components", 30 | "examples", 31 | "test", 32 | "docs", 33 | "src", 34 | "page", 35 | "gh-pages", 36 | "Gruntfile.js" 37 | ] 38 | } -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # exit with nonzero exit code if anything fails 3 | 4 | # Build command 5 | grunt build 6 | 7 | # clear and clone our gh-pages branch 8 | rm -rf gh-pages || exit 0; 9 | git clone --branch=gh-pages "git://${GH_REF}" gh-pages 10 | 11 | grunt gh-pages 12 | 13 | # Go to the branch 14 | cd gh-pages 15 | 16 | # inside this git repo we'll pretend to be a new user 17 | git config user.name "Travis CI" 18 | git config user.email "juampi92@gmail.com" 19 | 20 | # Add changes (based on the replacement done earlier) 21 | git add --all . 22 | git commit -m "Deploy to GitHub Pages" || true # Or true, so if there's nothing to commit, don't trow an error 23 | 24 | # Force push gh-pages branch (current one) with the changes made from the build 25 | # We redirect any output to /dev/null to hide any sensitive credential data that might otherwise be exposed. 26 | git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" > /dev/null 2>&1 -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gsvpano.js 8 | 35 | 36 | 37 | 38 | 39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | 47 | 48 | 102 | 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gsvpano", 3 | "pkgname": "GSVPano", 4 | "version": "1.1.0", 5 | "description": "Google Street View Panorama Util", 6 | "main": "src/GSVPano.js", 7 | "directories": { 8 | "example": "examples" 9 | }, 10 | "scripts": { 11 | "test": "mocha --reporter spec" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/juampi92/GSVPano" 16 | }, 17 | "keywords": [ 18 | "Google", 19 | "Street", 20 | "View", 21 | "Panorama" 22 | ], 23 | "author": "konforti, juampi92", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/juampi92/GSVPano/issues" 27 | }, 28 | "homepage": "https://juampi92.github.io/GSVPano", 29 | "dependencies": { 30 | "event-emitter": "^0.3.3" 31 | }, 32 | "devDependencies": { 33 | "chai": "^3.2.0", 34 | "grunt": "^0.4.5", 35 | "grunt-browserify": "^4.0.1", 36 | "grunt-contrib-clean": "^0.6.0", 37 | "grunt-contrib-copy": "^0.8.1", 38 | "grunt-contrib-jshint": "^0.11.2", 39 | "grunt-contrib-uglify": "^0.9.2", 40 | "grunt-contrib-yuidoc": "^0.10.0", 41 | "grunt-mocha-test": "^0.12.7", 42 | "grunt-watchify": "^0.1.0", 43 | "mocha": "^2.3.0", 44 | "yuidoc-bootstrap-theme": "https://github.com/juampi92/yuidoc-bootstrap-theme/tarball/master" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /page/assets/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Roboto', sans-serif; 3 | text-align: center; 4 | } 5 | 6 | section { 7 | height: auto; 8 | margin: 0 auto; 9 | width: 100%; 10 | position: relative; 11 | padding: 50px 0; 12 | background-color: white; 13 | } 14 | 15 | .parallax { 16 | box-shadow: 0 0 -50px rgba(0,0,0,0.8); 17 | padding: 100px 0; 18 | color: white; 19 | font-weight: bold; 20 | text-shadow: black 1px 1px 3px; 21 | } 22 | .parallax h1 { 23 | font-size: 50px; 24 | text-shadow: black 1px 1px 5px; 25 | } 26 | .parallax.bg_antarctica { 27 | background: url(http://i.imgur.com/ydPtSRN.png) 0% 0 fixed; 28 | /*background: white;*/ 29 | padding: 100px 0; 30 | } 31 | .parallax.bg_stonehenge { 32 | background: url(http://i.imgur.com/W58Pou1.png) 50% 0 fixed; 33 | } 34 | .parallax.bg_timessquare { 35 | background: url(http://i.imgur.com/vsSz3yR.png) 50% 0 fixed; 36 | } -------------------------------------------------------------------------------- /page/assets/js/script.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | // cache the window object 3 | $window = $(window); 4 | 5 | $('section[data-type="background"]').each(function() { 6 | // declare the variable to affect the defined data-type 7 | var $scroll = $(this), 8 | start = parseInt($scroll.data('start')) || 0; 9 | 10 | $scroll.css({ 11 | backgroundPosition: '50% ' + start + 'px' 12 | }); 13 | 14 | $(window).scroll(function() { 15 | // HTML5 proves useful for helping with creating JS functions! 16 | // also, negative value because we're scrolling upwards 17 | var yPos = -($window.scrollTop() / $scroll.data('speed')) + start; 18 | 19 | // background position 20 | var coords = '50% ' + yPos + 'px'; 21 | 22 | // move the background 23 | $scroll.css({ 24 | backgroundPosition: coords 25 | }); 26 | }); // end window scroll 27 | }); // end section function 28 | }); // close out script 29 | 30 | /* Create HTML5 element for IE */ 31 | document.createElement("section"); -------------------------------------------------------------------------------- /page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Google Street View Pano - Project Page 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 33 | 34 |
35 |
36 |

GSVPano.js

37 |

Library to help requesting and stitching Google Street View panoramas.

38 |
39 |
40 | 41 |
42 |
43 |

Google Street View Panorama

44 |

Download Google SV images, knowing only it's coordinates. Easy to use, easy to escalate.

45 |
46 | Check an Example 47 |
48 | 49 |
50 | 51 |
52 |
53 |

Fully documented

54 | View Documentation 55 |
56 |
57 | 58 |
59 |
60 |

Get it working only with a few lines!

61 |

Check out this simple example:

62 |
63 |
// Create a PanoLoader object
 64 | var loader = new GSVPANO.PanoLoader({
 65 |     zoom: 3
 66 | });
 67 | 
 68 | // Implement the onPanoramaLoad handler
 69 | loader.on('panorama.load', function(panorama) {
 70 |     // panorama will now be loaded
 71 | 
 72 |     // for individual progress
 73 |     panorama.on('progress', changeProgress); 
 74 |   
 75 |     // for individual complete callback
 76 |     panorama.on('complete', completeCallback);
 77 | });
 78 | 
 79 | // Invoke the load method with a LatLng point
 80 | loader.load( new google.maps.LatLng( 42.216188,-75.726578 ) );
 81 | 
82 |
83 |
84 |
85 | 86 |
87 |
88 |

Start using it now!

89 | Download 90 | Download minified 91 |
92 |
93 | 94 |
95 |
96 |

Github CDN

97 |

Check out other builds here.

98 |
<!-- Latest compiled and minified -->
 99 | <script src="//juampi92.github.io/GSVPano/build/GSVPano.min.js"></script>
100 | 
101 | <!-- Not minified -->
102 | <script src="//juampi92.github.io/GSVPano/build/GSVPano.js"></script>
103 | 
104 | <!-- Custom version -->
105 | <script src="//juampi92.github.io/GSVPano/build/GSVPano-1.0.0.min.js"></script>
106 | 
107 | 108 |

Install with Bower

109 |
110 |
$ bower install GSVPano
111 |
112 | 113 |

Install with npm

114 |

You can also install GSVPano using npm:

115 |
116 |
$ npm install gsvpano
117 |
118 |

require('gsvpano') will get you the library, and it's ready for you to use it with Browserify or whichever front end bundler you want.

119 |
120 |
121 | 122 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/EventEmitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://www.npmjs.com/package/event-emitter 3 | * @module EventEmitter 4 | * @class EventEmitter 5 | */ 6 | /** 7 | * Catches the event emission 8 | * @method on 9 | * @param {String} eventName 10 | * @param {Function} callback 11 | * @chainable 12 | */ 13 | /** 14 | * Fires the event for everyone to catch 15 | * @method emit 16 | * @param {String} eventName 17 | * @param {Amy} params Parameters to be recieved when subscribed 18 | * @private 19 | */ -------------------------------------------------------------------------------- /src/GSVPano.js: -------------------------------------------------------------------------------- 1 | // GSVPano.js 2 | // Copyright (c) 2014 Heganoo 3 | // https://github.com/heganoo/GSVPano 4 | 5 | var eventEmitter = require('event-emitter'); 6 | 7 | /** 8 | * @module GSVPANO 9 | * @author Hegano 10 | * @author juampi92 11 | */ 12 | var GSVPANO = GSVPANO || {}; 13 | 14 | 15 | /** 16 | * Fetch URL. Use this parameter in case the URL stops working. At 17 | * the end of this string, the parameters &panoid, &x, &y, &zoom 18 | * and the current timestamp are appended. 19 | * @property GSVPANO._url 20 | * @type {String} 21 | * @default 'http://maps.google.com/cbk?output=tile' 22 | */ 23 | // 'https://geo0.ggpht.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en&output=tile&nbt&fover=2' 24 | // 'https://cbks2.google.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en&output=tile' 25 | 26 | GSVPANO._url = 'http://maps.google.com/cbk?output=tile'; 27 | 28 | /** 29 | * Data Fetch URL. Use this parameter in case the URL stops working. 30 | * At the end of this string, the parameter &ll is appended. 31 | * @property GSVPANO._data_url 32 | * @type {String} 33 | * @default 'https://cbks0.google.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en&output=polygon&it=1%3A1&rank=closest&radius=350' 34 | */ 35 | GSVPANO._data_url = 'https://cbks0.google.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en&output=polygon&it=1%3A1&rank=closest&radius=350'; 36 | 37 | GSVPANO.Pano = require('./Pano'); 38 | 39 | /** 40 | * @class PanoLoader 41 | * @extends {EventEmitter} 42 | * @constructor 43 | * @param {Object} parameters 44 | * @param {Number} parameters.zoom Zoom (default 1) 45 | * @param {Number} parameters.autocompose Compose automatically (default true) 46 | * @example 47 | * var loader = new GSVPANO.PanoLoader({ zoom: 3, autocompose: false }); 48 | */ 49 | GSVPANO.PanoLoader = function(parameters) { 50 | 'use strict'; 51 | 52 | eventEmitter(this); 53 | 54 | var _params = parameters || {}; 55 | 56 | this._panoClient = new google.maps.StreetViewService(); 57 | 58 | /** 59 | * @attribute zoom 60 | * @type {Number} 61 | * @default 1 62 | * @private 63 | */ 64 | this.setZoom(_params.zoom || 1); 65 | /** 66 | * Decides that when a Pano is added, it starts composing right away 67 | * @attribute autocompose 68 | * @type {Boolean} 69 | * @default true 70 | */ 71 | this.autocompose = (_params.autocompose === undefined) ? true : _params.autocompose; 72 | }; 73 | 74 | /** 75 | * @event panorama.data 76 | * @param {Pano} pano 77 | * @example 78 | * loader.on('panorama.data', function(pano){ 79 | * console.log('Pano ' + pano.id + ' added'); 80 | * }); 81 | */ 82 | 83 | /** 84 | * @event panorama.nodata 85 | * @param {Google.Maps.LatLng} location 86 | * @param {Google.Maps.StreetViewStatus} status 87 | */ 88 | 89 | /** 90 | * @event panorama.progress 91 | * @param {Number} p 92 | * @param {Pano} pano 93 | * @example 94 | * loader.on('progress', function(p, pano) { 95 | * console.log('Pano progress: ' + p + '%'); 96 | * }); 97 | */ 98 | GSVPANO.PanoLoader.prototype._setProgress = function(pano, p) { 99 | this.emit('panorama.progress', p, pano); 100 | }; 101 | /** 102 | * @event panorama.load 103 | * @param {Pano} pano 104 | * @example 105 | * loader.on('panorama.load', function(pano){ 106 | * $container.append(pano.canvas); 107 | * }); 108 | */ 109 | 110 | /** 111 | * @event error 112 | * @param {String} message 113 | * @example 114 | * loader.on('error', function(message){ 115 | * console.log(message) 116 | * }); 117 | */ 118 | GSVPANO.PanoLoader.prototype._hrowError = function(message) { 119 | this.emit('error', message); 120 | }; 121 | 122 | /** 123 | * Middle function for working with IDs. 124 | * @method loadData 125 | * @param {Google.Maps.Location} location 126 | * @deprecated Disabled right now 127 | */ 128 | /*this.loadData = function(location) { 129 | var self = this; 130 | var url; 131 | 132 | //url = 'https://maps.google.com/cbk?output=json&hl=x-local&ll=' + location.lat() + ',' + location.lng() + '&cb_client=maps_sv&v=3'; 133 | url = GSVPANO._data_url + '&ll=' + location.lat() + ',' + location.lng(); 134 | 135 | var http_request = new XMLHttpRequest(); 136 | http_request.open("GET", url, true); 137 | http_request.onreadystatechange = function() { 138 | if (http_request.readyState == 4 && http_request.status == 200) { 139 | var data = JSON.parse(http_request.responseText); 140 | // self.loadPano(location, data.result[0].id); 141 | } 142 | }; 143 | http_request.send(null); 144 | };*/ 145 | 146 | /** 147 | * Fires panorama.data, panorama.nodata 148 | * @method load 149 | * @param {Google.Maps.Location} location 150 | * @param {Function} callback 151 | * @example 152 | * // Let the panorama.load event handle it's load 153 | * loader.load(new google.maps.LatLng(lat, lng)); 154 | * @example 155 | * // Also handle the load individually 156 | * loader.load(new google.maps.LatLng(lat, lng), function(pano){ 157 | * // This individual load has been completed 158 | * container.append(pano.canvas); 159 | * }); 160 | */ 161 | GSVPANO.PanoLoader.prototype.load = function(location, callback) { 162 | var self = this; 163 | 164 | this._panoClient.getPanoramaByLocation(location, 50, function(result, status) { 165 | 166 | if (status === google.maps.StreetViewStatus.OK) { 167 | 168 | var pano = new GSVPANO.Pano({ 169 | id: result.location.pano, 170 | rotation: result.tiles.centerHeading, 171 | pitch: result.tiles.originPitch, 172 | copyright: result.copyright, 173 | imageDate: result.imageDate, 174 | location: result.location, 175 | zoom: self.zoom 176 | }) 177 | .on('complete', self.emit.bind(self, 'panorama.load')) 178 | .on('progress', self._setProgress.bind(self, pano)); 179 | 180 | if (self.autocompose) { 181 | pano.compose(); 182 | } 183 | self.emit('panorama.data', pano); 184 | 185 | if (callback) { 186 | callback(pano); 187 | } 188 | } else { 189 | self.emit('panorama.nodata', location, status); 190 | self._throwError('Could not retrieve panorama for the following reason: ' + status); 191 | } 192 | }); 193 | }; 194 | 195 | /** 196 | * @method setZoom 197 | * @param {Number} z 198 | */ 199 | GSVPANO.PanoLoader.prototype.setZoom = function(z) { 200 | this.zoom = z; 201 | }; 202 | 203 | global.GSVPANO = module.exports = GSVPANO; -------------------------------------------------------------------------------- /src/Pano.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module GSVPANO 3 | */ 4 | 5 | var eventEmitter = require('event-emitter'); 6 | eventEmitter.alloff = require('event-emitter/all-off'); 7 | 8 | /** 9 | * One single Panoramic item 10 | * @class Pano 11 | * @author juampi92 12 | * @extends {EventEmitter} 13 | * @constructor 14 | * @param {Object} params 15 | * @param {Hash} params.id 16 | * @param {Number} params.rotation (on degrees) 17 | * @param {Number} params.pitch 18 | * @param {Google.Maps.LatLng} params.location 19 | * @param {String} params.copyright 20 | * @param {Date} params.imageDate 21 | * @param {Number} params.zoom 22 | * @example 23 | * var pano = new GSVPANO.Pano({ 24 | * id: panoId, 25 | * rotation: rotation, 26 | * pitch: pitch, 27 | * location: location, 28 | * imageDate: imageDate, 29 | * copyright: copyright, 30 | * zoom: zoom 31 | * }); 32 | */ 33 | var Pano = function(params) { 34 | eventEmitter(this); 35 | 36 | var _params = params || {}; 37 | 38 | /** 39 | * @attribute id 40 | * @type {Hash} 41 | */ 42 | this.id = params.id; 43 | /** 44 | * @attribute rotation 45 | * @type {Number} 46 | */ 47 | this.setRotation(params.rotation || 0); 48 | /** 49 | * @attribute pitch 50 | * @type {Number} 51 | */ 52 | this.pitch = params.pitch; 53 | /** 54 | * @attribute location 55 | * @type {Google.Maps.LatLng} 56 | */ 57 | this.location = params.location; 58 | /** 59 | * @attribute imageDate 60 | * @type {Date} 61 | */ 62 | this.imageDate = params.imageDate; 63 | /** 64 | * @attribute copyright 65 | * @type {String} 66 | */ 67 | this.copyright = params.copyright; 68 | /** 69 | * @attribute zoom 70 | * @type {Number} 71 | */ 72 | this.zoom = parseInt(params.zoom); 73 | /** 74 | * @attribute canvas 75 | * @type {Canvas Element} 76 | * @default null 77 | */ 78 | this.canvas = null; 79 | /** 80 | * @attribute _ctx 81 | * @type {Canvas 2d Context} 82 | * @default null 83 | */ 84 | this._ctx = null; 85 | /** 86 | * @attribute _loaded 87 | * @type {Boolean} 88 | */ 89 | this._loaded = false; 90 | }; 91 | 92 | /** 93 | * Saves rotation. Input in degrees 94 | * @method setRotation 95 | * @param {Number} deg 96 | * @chainable 97 | */ 98 | Pano.prototype.setRotation = function(deg) { 99 | this.rotation = deg * Math.PI / 180.0; 100 | return this; 101 | }; 102 | 103 | /** 104 | * @method initCanvas 105 | * @private 106 | */ 107 | Pano.prototype.initCanvas = function() { 108 | this.canvas = document.createElement('canvas'); 109 | this._ctx = this.canvas.getContext('2d'); 110 | 111 | var w = 416 * Math.pow(2, this.zoom), 112 | h = (416 * Math.pow(2, this.zoom - 1)); 113 | this.canvas.width = w; 114 | this.canvas.height = h; 115 | }; 116 | 117 | /** 118 | * Progress notification 119 | * @event progress 120 | * @param {Number} p 121 | * @chainable 122 | * @example 123 | * pano.on('progress', function(p) { 124 | * console.log('Pano download progress: ' + p + '%'); 125 | * }); 126 | */ 127 | /** 128 | * Complete notification 129 | * @event complete 130 | * @param {Pano} pano 131 | * @chainable 132 | * @example 133 | * pano.on('complete', function(p) { 134 | * console.log('Pano completed progress: ' + p + '%'); 135 | * }); 136 | */ 137 | /** 138 | * Will fire 'callback' when completed 139 | * @method compose 140 | * @param {Hash} panoId 141 | * @chainable 142 | * @example 143 | * var pano = new Pano(...); 144 | * pano.compose(); 145 | */ 146 | Pano.prototype.compose = function() { 147 | this.initCanvas(); 148 | 149 | var w, 150 | h = Math.pow(2, this.zoom - 1), 151 | url, x, y; 152 | 153 | switch (this.zoom) { 154 | case 5: 155 | w = 26; 156 | h = 13; 157 | break; 158 | case 4: 159 | w = 13; 160 | h = 7; 161 | break; 162 | case 3: 163 | w = 7; 164 | break; 165 | default: 166 | w = Math.pow(2, this.zoom); 167 | } 168 | 169 | this._count = 0; 170 | this._total = w * h; 171 | 172 | // Get the tiles 173 | for (y = 0; y < h; y++) { 174 | for (x = 0; x < w; x++) { 175 | this.createImage(x, y); 176 | } 177 | } 178 | return this; 179 | }; 180 | 181 | /** 182 | * Creates an Image with the appropiate load callback 183 | * @method createImage 184 | * @param {Number} x 185 | * @param {Number} y 186 | * @private 187 | */ 188 | Pano.prototype.createImage = function(x, y) { 189 | var url = GSVPANO._url + '&panoid=' + this.id + '&zoom=' + this.zoom + '&x=' + x + '&y=' + y + '&' + Date.now(), 190 | img = new Image(); 191 | 192 | img.addEventListener('load', this.composeFromTile.bind(this, x, y, img)); 193 | img.crossOrigin = ''; 194 | img.src = url; 195 | }; 196 | 197 | /** 198 | * @method composeFromTile 199 | * @param {Number} x 200 | * @param {Number} y 201 | * @param {Image} texture 202 | * @private 203 | */ 204 | Pano.prototype.composeFromTile = function(x, y, texture) { 205 | // Complete this section of the frame 206 | this._ctx.drawImage(texture, x * 512, y * 512); 207 | this._count++; 208 | 209 | var p = Math.round(this._count * 100 / this._total); 210 | this.emit('progress', p); 211 | 212 | // If finished 213 | if (this._count === this._total) { 214 | // Done loading 215 | this._loaded = true; 216 | // Trigger complete event 217 | this.emit('complete', this); 218 | // Remove all events 219 | eventEmitter.alloff(this); 220 | } 221 | }; 222 | 223 | module.exports = Pano; --------------------------------------------------------------------------------