├── LICENSE ├── README.md ├── bower.json ├── canvas-layer.js ├── point-overlay.html ├── shader-program.js └── tornadoes-1950-2014.json /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # polymer-webgl-visualization 2 | Create a data visualization using Polymer and WebGL 3 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polymer-webgl-visualization", 3 | "version": "1.1.0", 4 | "homepage": "https://github.com/googlecodelabs/polymer-webgl-visualization", 5 | "authors": [ 6 | "Brendan Kenny " 7 | ], 8 | "description": "Resources to support the polymer-webgl-visualization codelab", 9 | "main": "point-overlay.html", 10 | "keywords": [ 11 | "polymer", 12 | "webgl", 13 | "google", 14 | "maps", 15 | "visualization", 16 | "codelab" 17 | ], 18 | "license": "Apache-2.0", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /canvas-layer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* exported createCanvasLayer */ 18 | 19 | // NOTE(bckenny): wrapped so it can be loaded after the Maps API (see prototype) 20 | function createCanvasLayer() { 21 | 22 | /** 23 | * A map layer that provides a canvas over the slippy map and a callback 24 | * system for efficient animation. Requires canvas and CSS 2D transform 25 | * support. 26 | * @constructor 27 | * @extends google.maps.OverlayView 28 | * @param {CanvasLayerOptions=} opt_options 29 | */ 30 | function CanvasLayer(opt_options) { 31 | /** 32 | * If true, canvas is in a map pane and the OverlayView is fully functional. 33 | * See google.maps.OverlayView.onAdd for more information. 34 | * @private {boolean} 35 | */ 36 | this.isAdded_ = false; 37 | 38 | /** 39 | * The name of the MapPane in which this layer will be displayed. 40 | * @private {string} 41 | */ 42 | this.paneName_ = CanvasLayer.DEFAULT_PANE_NAME_; 43 | 44 | /** 45 | * A user-supplied function called whenever an update is required. Null or 46 | * undefined if a callback is not provided. 47 | * @private {?function()} 48 | */ 49 | this.updateHandler_ = null; 50 | 51 | /** 52 | * A user-supplied function called whenever an update is required and the 53 | * map has been resized since the last update. Null or undefined if a 54 | * callback is not provided. 55 | * @private {?function()} 56 | */ 57 | this.resizeHandler_ = null; 58 | 59 | /** 60 | * The LatLng coordinate of the top left of the current view of the map. 61 | * Will be null when this.isAdded_ is false. 62 | * @private {google.maps.LatLng} 63 | */ 64 | this.topLeft_ = null; 65 | 66 | /** 67 | * The map-pan event listener. Will be null when this.isAdded_ is false. 68 | * @private {google.maps.MapsEventListener} 69 | */ 70 | this.centerListener_ = null; 71 | 72 | /** 73 | * The map-resize event listener. Will be null when this.isAdded_ is false. 74 | * @private {google.maps.MapsEventListener} 75 | */ 76 | this.resizeListener_ = null; 77 | 78 | /** 79 | * If true, the map size has changed and this.resizeHandler_ must be called 80 | * on the next update. 81 | * @private {boolean} 82 | */ 83 | this.needsResize_ = true; 84 | 85 | /** 86 | * A browser-defined id for the currently requested callback. Null when no 87 | * callback is queued. 88 | * @private {?number} 89 | */ 90 | this.requestAnimationFrameId_ = null; 91 | 92 | var canvas = document.createElement('canvas'); 93 | canvas.style.position = 'absolute'; 94 | canvas.style.top = 0; 95 | canvas.style.left = 0; 96 | canvas.style.pointerEvents = 'none'; 97 | 98 | /** 99 | * The canvas element. 100 | * @type {!HTMLCanvasElement} 101 | */ 102 | this.canvas = canvas; 103 | 104 | /** 105 | * The CSS width of the canvas, which may be different than the width of the 106 | * backing store. 107 | * @private {number} 108 | */ 109 | this.canvasCssWidth_ = 300; 110 | 111 | /** 112 | * The CSS height of the canvas, which may be different than the height of 113 | * the backing store. 114 | * @private {number} 115 | */ 116 | this.canvasCssHeight_ = 150; 117 | 118 | /** 119 | * A value for scaling the CanvasLayer resolution relative to the 120 | * CanvasLayer display size. 121 | * @private {number} 122 | */ 123 | this.resolutionScale_ = 1; 124 | 125 | /** 126 | * Simple bind for functions with no args for bind-less browsers (Safari). 127 | * @param {Object} thisArg The this value used for the target function. 128 | * @param {function()} func The function to be bound. 129 | */ 130 | function simpleBindShim(thisArg, func) { 131 | return function() { func.apply(thisArg); }; 132 | } 133 | 134 | /** 135 | * A reference to this.repositionCanvas_ with this bound as its this value. 136 | * @private {function(this:CanvasLayer)} 137 | */ 138 | this.repositionFunction_ = simpleBindShim(this, function() { 139 | this.repositionCanvas_(); 140 | this.update_(); 141 | }); 142 | 143 | /** 144 | * A reference to this.resize_ with this bound as its this value. 145 | * @private {function(this:CanvasLayer)} 146 | */ 147 | this.resizeFunction_ = simpleBindShim(this, this.resize_); 148 | 149 | /** 150 | * A reference to this.update_ with this bound as its this value. 151 | * @private {function(this:CanvasLayer)} 152 | */ 153 | this.requestUpdateFunction_ = simpleBindShim(this, this.update_); 154 | 155 | // set provided options, if any 156 | if (opt_options) { 157 | this.setOptions(opt_options); 158 | } 159 | } 160 | 161 | CanvasLayer.prototype = new google.maps.OverlayView(); 162 | 163 | /** 164 | * The default MapPane to contain the canvas. 165 | * @private {string} 166 | * @const 167 | */ 168 | CanvasLayer.DEFAULT_PANE_NAME_ = 'overlayLayer'; 169 | 170 | /** 171 | * Transform CSS property name, with vendor prefix if required. If browser 172 | * does not support transforms, property will be ignored. 173 | * @private {string} 174 | * @const 175 | */ 176 | CanvasLayer.CSS_TRANSFORM_ = (function() { 177 | var div = document.createElement('div'); 178 | var transformProps = [ 179 | 'transform', 180 | 'WebkitTransform', 181 | 'MozTransform', 182 | 'OTransform', 183 | 'msTransform' 184 | ]; 185 | for (var i = 0; i < transformProps.length; i++) { 186 | var prop = transformProps[i]; 187 | if (div.style[prop] !== undefined) { 188 | return prop; 189 | } 190 | } 191 | 192 | // return unprefixed version by default 193 | return transformProps[0]; 194 | })(); 195 | 196 | /** 197 | * The requestAnimationFrame function, with vendor-prefixed or 198 | * setTimeout-based fallbacks. Must be called with window as thisArg. 199 | * @param {function()} callback 200 | * @return {number} 201 | * @private 202 | */ 203 | CanvasLayer.prototype.requestAnimFrame_ = 204 | window.requestAnimationFrame || 205 | window.webkitRequestAnimationFrame || 206 | window.mozRequestAnimationFrame || 207 | window.oRequestAnimationFrame || 208 | window.msRequestAnimationFrame || 209 | function(callback) { 210 | return window.setTimeout(callback, 1000 / 60); 211 | }; 212 | 213 | /** 214 | * The cancelAnimationFrame function, with vendor-prefixed fallback. Does not 215 | * fall back to clearTimeout as some platforms implement requestAnimationFrame 216 | * but not cancelAnimationFrame, and the cost is an extra frame on onRemove. 217 | * Must be called with window as thisArg. 218 | * @param {number=} requestId The id of the frame request to cancel. 219 | * @private 220 | */ 221 | CanvasLayer.prototype.cancelAnimFrame_ = 222 | window.cancelAnimationFrame || 223 | window.webkitCancelAnimationFrame || 224 | window.mozCancelAnimationFrame || 225 | window.oCancelAnimationFrame || 226 | window.msCancelAnimationFrame || 227 | function(requestId) {}; 228 | 229 | /** 230 | * Sets any options provided. See CanvasLayerOptions for more information. 231 | * @param {CanvasLayerOptions} options The options to set. 232 | */ 233 | CanvasLayer.prototype.setOptions = function(options) { 234 | if (options.paneName !== undefined) { 235 | this.setPane(options.paneName); 236 | } 237 | 238 | if (options.updateHandler !== undefined) { 239 | this.setUpdateHandler(options.updateHandler); 240 | } 241 | 242 | if (options.resizeHandler !== undefined) { 243 | this.setResizeHandler(options.resizeHandler); 244 | } 245 | 246 | if (options.resolutionScale !== undefined) { 247 | this.setResolutionScale(options.resolutionScale); 248 | } 249 | 250 | if (options.map !== undefined) { 251 | this.setMap(options.map); 252 | } 253 | }; 254 | 255 | /** 256 | * Set the MapPane in which this layer will be displayed, by name. See 257 | * {@code google.maps.MapPanes} for the panes available. 258 | * @param {string} paneName The name of the desired MapPane. 259 | */ 260 | CanvasLayer.prototype.setPaneName = function(paneName) { 261 | this.paneName_ = paneName; 262 | 263 | this.setPane_(); 264 | }; 265 | 266 | /** 267 | * @return {string} The name of the current container pane. 268 | */ 269 | CanvasLayer.prototype.getPaneName = function() { 270 | return this.paneName_; 271 | }; 272 | 273 | /** 274 | * Adds the canvas to the specified container pane. Since this is guaranteed 275 | * to execute only after onAdd is called, this is when paneName's existence is 276 | * checked (and an error is thrown if it doesn't exist). 277 | * @private 278 | */ 279 | CanvasLayer.prototype.setPane_ = function() { 280 | if (!this.isAdded_) { 281 | return; 282 | } 283 | 284 | // onAdd has been called, so panes can be used 285 | var panes = this.getPanes(); 286 | if (!panes[this.paneName_]) { 287 | throw new Error('"' + this.paneName_ + '" is not a valid MapPane name.'); 288 | } 289 | 290 | panes[this.paneName_].appendChild(this.canvas); 291 | }; 292 | 293 | /** 294 | * Set a function that will be called whenever the parent map and the 295 | * overlay's canvas have been resized. If opt_resizeHandler is null or 296 | * unspecified, any existing callback is removed. 297 | * @param {?function()} opt_resizeHandler 298 | */ 299 | CanvasLayer.prototype.setResizeHandler = function(opt_resizeHandler) { 300 | this.resizeHandler_ = opt_resizeHandler; 301 | }; 302 | 303 | /** 304 | * Sets a value for scaling the canvas resolution relative to the canvas 305 | * display size. This can be used to save computation by scaling the backing 306 | * buffer down, or to support high DPI devices by scaling it up (by e.g. 307 | * window.devicePixelRatio). 308 | * @param {number} scale 309 | */ 310 | CanvasLayer.prototype.setResolutionScale = function(scale) { 311 | if (typeof scale === 'number') { 312 | this.resolutionScale_ = scale; 313 | this.resize_(); 314 | } 315 | }; 316 | 317 | /** 318 | * Set a function that will be called when a repaint of the canvas is 319 | * required. If opt_updateHandler is null or unspecified, any existing 320 | * callback is removed. 321 | * @param {?function()} opt_updateHandler The update callback function. 322 | */ 323 | CanvasLayer.prototype.setUpdateHandler = function(opt_updateHandler) { 324 | this.updateHandler_ = opt_updateHandler; 325 | }; 326 | 327 | /** 328 | * @inheritDoc 329 | */ 330 | CanvasLayer.prototype.onAdd = function() { 331 | if (this.isAdded_) { 332 | return; 333 | } 334 | 335 | this.isAdded_ = true; 336 | this.setPane_(); 337 | 338 | this.resizeListener_ = google.maps.event.addListener(this.getMap(), 339 | 'resize', this.resizeFunction_); 340 | this.centerListener_ = google.maps.event.addListener(this.getMap(), 341 | 'center_changed', this.repositionFunction_); 342 | 343 | this.resize_(); 344 | this.repositionCanvas_(); 345 | this.scheduleUpdate(); 346 | }; 347 | 348 | /** 349 | * @inheritDoc 350 | */ 351 | CanvasLayer.prototype.onRemove = function() { 352 | if (!this.isAdded_) { 353 | return; 354 | } 355 | 356 | this.isAdded_ = false; 357 | this.topLeft_ = null; 358 | 359 | // remove canvas and listeners for pan and resize from map 360 | this.canvas.parentElement.removeChild(this.canvas); 361 | if (this.centerListener_) { 362 | google.maps.event.removeListener(this.centerListener_); 363 | this.centerListener_ = null; 364 | } 365 | if (this.resizeListener_) { 366 | google.maps.event.removeListener(this.resizeListener_); 367 | this.resizeListener_ = null; 368 | } 369 | 370 | // cease canvas update callbacks 371 | if (this.requestAnimationFrameId_) { 372 | this.cancelAnimFrame_.call(window, this.requestAnimationFrameId_); 373 | this.requestAnimationFrameId_ = null; 374 | } 375 | }; 376 | 377 | /** 378 | * The internal callback for resize events that resizes the canvas to keep the 379 | * map properly covered. 380 | * @private 381 | */ 382 | CanvasLayer.prototype.resize_ = function() { 383 | if (!this.isAdded_) { 384 | return; 385 | } 386 | 387 | var map = this.getMap(); 388 | var mapWidth = map.getDiv().offsetWidth; 389 | var mapHeight = map.getDiv().offsetHeight; 390 | 391 | var newWidth = mapWidth * this.resolutionScale_; 392 | var newHeight = mapHeight * this.resolutionScale_; 393 | var oldWidth = this.canvas.width; 394 | var oldHeight = this.canvas.height; 395 | 396 | // resizing may allocate a new back buffer, so do so conservatively 397 | if (oldWidth !== newWidth || oldHeight !== newHeight) { 398 | this.canvas.width = newWidth; 399 | this.canvas.height = newHeight; 400 | 401 | this.needsResize_ = true; 402 | this.scheduleUpdate(); 403 | } 404 | 405 | // reset styling if new sizes don't match; resize of data not needed 406 | if (this.canvasCssWidth_ !== mapWidth || 407 | this.canvasCssHeight_ !== mapHeight) { 408 | this.canvasCssWidth_ = mapWidth; 409 | this.canvasCssHeight_ = mapHeight; 410 | this.canvas.style.width = mapWidth + 'px'; 411 | this.canvas.style.height = mapHeight + 'px'; 412 | } 413 | }; 414 | 415 | /** 416 | * @inheritDoc 417 | */ 418 | CanvasLayer.prototype.draw = function() { 419 | this.repositionCanvas_(); 420 | this.scheduleUpdate(); 421 | }; 422 | 423 | /** 424 | * Internal callback for map view changes. Since the Maps API moves the 425 | * overlay along with the map, this function calculates the opposite 426 | * translation to keep the canvas in place. 427 | * @private 428 | */ 429 | CanvasLayer.prototype.repositionCanvas_ = function() { 430 | // TODO(bckenny): *should* only be executed on RAF, but in current browsers 431 | // this causes noticeable hitches in map and overlay relative 432 | // positioning. 433 | 434 | var map = this.getMap(); 435 | 436 | // topLeft can't be calculated from map.getBounds(), because bounds are 437 | // clamped to -180 and 180 when completely zoomed out. Instead, calculate 438 | // left as an offset from the center, which is an unwrapped LatLng. 439 | var top = map.getBounds().getNorthEast().lat(); 440 | var center = map.getCenter(); 441 | var scale = Math.pow(2, map.getZoom()); 442 | var left = center.lng() - (this.canvasCssWidth_ * 180) / (256 * scale); 443 | this.topLeft_ = new google.maps.LatLng(top, left); 444 | 445 | // Canvas position relative to draggable map's container depends on 446 | // overlayView's projection, not the map's. Have to use the center of the 447 | // map for this, not the top left, for the same reason as above. 448 | var projection = this.getProjection(); 449 | var divCenter = projection.fromLatLngToDivPixel(center); 450 | var offsetX = -Math.round(this.canvasCssWidth_ / 2 - divCenter.x); 451 | var offsetY = -Math.round(this.canvasCssHeight_ / 2 - divCenter.y); 452 | this.canvas.style[CanvasLayer.CSS_TRANSFORM_] = 'translate(' + 453 | offsetX + 'px,' + offsetY + 'px)'; 454 | }; 455 | 456 | /** 457 | * Internal callback that serves as main animation scheduler via 458 | * requestAnimationFrame. Calls resize and update callbacks if set. 459 | * @private 460 | */ 461 | CanvasLayer.prototype.update_ = function() { 462 | this.requestAnimationFrameId_ = null; 463 | 464 | if (!this.isAdded_) { 465 | return; 466 | } 467 | 468 | if (this.needsResize_ && this.resizeHandler_) { 469 | this.needsResize_ = false; 470 | this.resizeHandler_(); 471 | } 472 | 473 | if (this.updateHandler_) { 474 | this.updateHandler_(); 475 | } 476 | }; 477 | 478 | /** 479 | * A convenience method to get the current LatLng coordinate of the top left 480 | * of the current view of the map. 481 | * @return {google.maps.LatLng} The top left coordinate. 482 | */ 483 | CanvasLayer.prototype.getTopLeft = function() { 484 | return this.topLeft_; 485 | }; 486 | 487 | /** 488 | * Schedule a requestAnimationFrame callback to updateHandler. If one is 489 | * already scheduled, there is no effect. 490 | */ 491 | CanvasLayer.prototype.scheduleUpdate = function() { 492 | if (this.isAdded_ && !this.requestAnimationFrameId_) { 493 | this.requestAnimationFrameId_ = 494 | this.requestAnimFrame_.call(window, this.requestUpdateFunction_); 495 | } 496 | }; 497 | 498 | return CanvasLayer; 499 | } 500 | -------------------------------------------------------------------------------- /point-overlay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 21 | 558 | -------------------------------------------------------------------------------- /shader-program.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /* global Promise */ 17 | /* exported ShaderProgram */ 18 | 19 | /** 20 | * Create a WebGL shader program, which consists of a linked vertex shader and 21 | * fragment shader. Shader source can be provided or modified at any time by 22 | * calling setVertexShader or setFragmentShader as appropriate, but then the 23 | * shaders must be manually linked to make this ShaderProgram use the new code. 24 | * If vertex and fragment shader source are provided to the constructor, 25 | * program compilation and linking is attempted immediately. 26 | * @param {!WebGLRenderingContext} gl The WebGL context. 27 | * @param {string=} opt_vertexSrc The GLSL ES vertex shader source code. 28 | * @param {string=} opt_fragmentSrc The GLSL ES fragment shader source code. 29 | * @constructor 30 | * @struct 31 | */ 32 | var ShaderProgram = function(gl, opt_vertexSrc, opt_fragmentSrc) { 33 | /** 34 | * The WebGL context. 35 | * @private {!WebGLRenderingContext} 36 | */ 37 | this.gl_ = gl; 38 | 39 | /** 40 | * Collection of active attribute locations in this program object, keyed 41 | * on attribute name. Unless new shaders are linked in this program and 42 | * attribute locations change, use these rather than query for them. 43 | * @type {!Object} 44 | */ 45 | this.attributes = {}; 46 | 47 | /** 48 | * Collection of uniform locations and setters, keyed on uniform name. Raw 49 | * uniform locations are at thisProgram.uniforms.uniformName.location, while 50 | * thisProgram.uniforms.uniformName itself is a setter, used for setting 51 | * scalars, vectors, or matrices, where appropriate. 52 | * @type {Object} 53 | */ 54 | this.uniforms = {}; 55 | 56 | /** 57 | * The vertex shader object. 58 | * @private {!WebGLShader} 59 | */ 60 | this.vertexShader_ = gl.createShader(gl.VERTEX_SHADER); 61 | 62 | /** 63 | * The vertex shader has been successfully compiled. 64 | * @private {boolean} 65 | */ 66 | this.vertexCompiled_ = false; 67 | 68 | /** 69 | * The fragment shader object. 70 | * @private {!WebGLShader} 71 | */ 72 | this.fragmentShader_ = gl.createShader(gl.FRAGMENT_SHADER); 73 | 74 | /** 75 | * The fragment shader has been successfully compiled. 76 | * @private {boolean} 77 | */ 78 | this.fragmentCompiled_ = false; 79 | 80 | /** 81 | * The raw WebGL program object. 82 | * @type {!WebGLProgram} 83 | */ 84 | this.program = gl.createProgram(); 85 | 86 | /** 87 | * The program has been successfully linked. 88 | * @private {boolean} 89 | */ 90 | this.programLinked_ = false; 91 | 92 | gl.attachShader(this.program, this.vertexShader_); 93 | gl.attachShader(this.program, this.fragmentShader_); 94 | 95 | // if shader sources have been provided, compile them now and attempt to link 96 | if (opt_vertexSrc) { 97 | this.setVertexShader(opt_vertexSrc); 98 | } 99 | if (opt_fragmentSrc) { 100 | this.setFragmentShader(opt_fragmentSrc); 101 | } 102 | if (this.vertexCompiled_ && this.fragmentCompiled_) { 103 | this.link(); 104 | } 105 | }; 106 | 107 | /** 108 | * Lookup table from uniform type to uniform setter function names. Keys are 109 | * from OpenGL/WebGL spec. 110 | * @enum {string} 111 | * @private 112 | */ 113 | ShaderProgram.UNIFORM_SETTERS_ = { 114 | 0x1406: 'uniform1f', /* FLOAT */ 115 | 0x8b50: 'uniform2f', /* FLOAT_VEC2 */ 116 | 0x8b51: 'uniform3f', /* FLOAT_VEC3 */ 117 | 0x8b52: 'uniform4f', /* FLOAT_VEC4 */ 118 | 0x1404: 'uniform1i', /* INT */ 119 | 0x8b53: 'uniform2i', /* INT_VEC2 */ 120 | 0x8b54: 'uniform3i', /* INT_VEC3 */ 121 | 0x8b55: 'uniform4i', /* INT_VEC4 */ 122 | 0x8b56: 'uniform1i', /* BOOL */ 123 | 0x8b57: 'uniform2i', /* BOOL_VEC2 */ 124 | 0x8b58: 'uniform3i', /* BOOL_VEC3 */ 125 | 0x8b59: 'uniform4i', /* BOOL_VEC4 */ 126 | 0x8b5e: 'uniform1i', /* SAMPLER_2D */ 127 | 0x8b60: 'uniform1i', /* SAMPLER_CUBE */ 128 | }; 129 | 130 | /** 131 | * Lookup table from matrix uniform types to uniform setter function names. 132 | * Matrices can only be supplied as arrays (setter functions ending with 'v'), 133 | * so are special-cased here. Keys are from OpenGL/WebGL spec. 134 | * @enum {string} 135 | * @private 136 | */ 137 | ShaderProgram.UNIFORM_MATRIX_SETTERS_ = { 138 | 0x8b5a: 'uniformMatrix2fv', /* FLOAT_MAT2 */ 139 | 0x8b5b: 'uniformMatrix3fv', /* FLOAT_MAT3 */ 140 | 0x8b5c: 'uniformMatrix4fv', /* FLOAT_MAT4 */ 141 | }; 142 | 143 | /** 144 | * Create a ShaderProgram from shader source at vertexUrl and fragmentUrl. 145 | * Returns a promise that is fulfilled by returning the compiled and linked 146 | * ShaderProgram. Promise rejection will reveal either failure to fetch the 147 | * supplied URL(s) or compilation and linking failure. 148 | * @param {string} vertexUrl URL for vertex shader source. 149 | * @param {string} fragmentUrl URL for the fragment shader. 150 | * @return {!Promise} 151 | */ 152 | ShaderProgram.fromXhr = function(gl, vertexUrl, fragmentUrl) { 153 | var program = new ShaderProgram(gl); 154 | 155 | return Promise.all([ 156 | ShaderProgram.promiseXhr_(vertexUrl) 157 | .then(program.setVertexShader.bind(program)), 158 | ShaderProgram.promiseXhr_(fragmentUrl) 159 | .then(program.setFragmentShader.bind(program)) 160 | ]).then(function() { 161 | program.link(); 162 | return program; 163 | }); 164 | }; 165 | 166 | /** 167 | * Fetches a resource via XHR and returns a promise that will be fulfilled with 168 | * its result. 169 | * @param {string} url 170 | * @return {!Promise} 171 | * @private 172 | */ 173 | ShaderProgram.promiseXhr_ = function(url) { 174 | return new Promise(function(resolve, reject) { 175 | var xhr = new XMLHttpRequest(); 176 | xhr.open('GET', url); 177 | xhr.onload = function(e) { 178 | if (this.status === 200) { 179 | resolve(this.response); 180 | } else { 181 | reject(this.statusText); 182 | } 183 | }; 184 | xhr.onerror = function(e) { 185 | reject(this.statusText); 186 | }; 187 | xhr.send(); 188 | }); 189 | }; 190 | 191 | /** 192 | * Returns true if the shaders have succesfully compiled and have been linked. 193 | * @return {boolean} 194 | */ 195 | ShaderProgram.prototype.isReady = function() { 196 | return this.programLinked_; 197 | }; 198 | 199 | /** 200 | * Compiles a vertex or fragment shader from the supplied source code. 201 | * @param {string} src 202 | * @param {!WebGLShader} shader 203 | * @return {boolean} Whether the shader compiled successfully. 204 | * @private 205 | */ 206 | ShaderProgram.prototype.compileShader_ = function(src, shader) { 207 | this.gl_.shaderSource(shader, src); 208 | this.gl_.compileShader(shader); 209 | 210 | var compileStatus = this.gl_.getShaderParameter(shader, 211 | this.gl_.COMPILE_STATUS); 212 | 213 | // invalidate current program 214 | this.programLinked_ = false; 215 | 216 | return compileStatus; 217 | }; 218 | 219 | /** 220 | * Sets the source of the fragment shader and attempts to compile it. If 221 | * compilation fails, an error is thrown with the compiler log within it. 222 | * Regardless of success, this shader program will continue to operate as the 223 | * previous program (if there was one) until link() is called and executed 224 | * successfully. 225 | * @param {string} src 226 | * @throws {Error} 227 | */ 228 | ShaderProgram.prototype.setFragmentShader = function(src) { 229 | this.fragmentCompiled_ = this.compileShader_(src, this.fragmentShader_); 230 | 231 | if (!this.fragmentCompiled_) { 232 | throw new Error('Fragment shader failed to compile. Log: ' + 233 | this.getFragmentShaderInfoLog()); 234 | } 235 | }; 236 | 237 | /** 238 | * Sets the source of the vertex shader and attempts to compile it. If 239 | * compilation fails, an error is thrown with the compiler log within it. 240 | * Regardless of success, this shader program will continue to operate as the 241 | * previous program (if there was one) until link() is called and executed 242 | * successfully. 243 | * @param {string} src 244 | * @throws {Error} 245 | */ 246 | ShaderProgram.prototype.setVertexShader = function(src) { 247 | this.vertexCompiled_ = this.compileShader_(src, this.vertexShader_); 248 | 249 | if (!this.vertexCompiled_) { 250 | throw new Error('Vertex shader failed to compile. Log: ' + 251 | this.getVertexShaderInfoLog()); 252 | } 253 | }; 254 | 255 | /** 256 | * Returns the contents of the information log for the currently attached 257 | * fragment shader, if any. 258 | * @return {string} 259 | */ 260 | ShaderProgram.prototype.getFragmentShaderInfoLog = function() { 261 | return this.gl_.getShaderInfoLog(this.fragmentShader_); 262 | }; 263 | 264 | /** 265 | * Returns the contents of the information log for this program object, if any. 266 | * @return {string} 267 | */ 268 | ShaderProgram.prototype.getProgramInfoLog = function() { 269 | return this.gl_.getProgramInfoLog(this.program); 270 | }; 271 | 272 | /** 273 | * Returns the contents of the information log for the currently attached 274 | * vertex shader, if any. 275 | * @return {string} 276 | */ 277 | ShaderProgram.prototype.getVertexShaderInfoLog = function() { 278 | return this.gl_.getShaderInfoLog(this.vertexShader_); 279 | }; 280 | 281 | /** 282 | * Enumerate all active attribute locations in this program object and place on 283 | * this.attributes. Previous enumeration of attribute locations is discarded. 284 | * @private 285 | */ 286 | ShaderProgram.prototype.initAttributes_ = function() { 287 | var count = this.gl_.getProgramParameter( 288 | this.program, this.gl_.ACTIVE_ATTRIBUTES); 289 | 290 | // clear attribute locations and re-enumerate from currently linked program 291 | this.attributes = {}; 292 | for (var i = 0; i < count; i++) { 293 | var info = this.gl_.getActiveAttrib(this.program, i); 294 | var loc = this.gl_.getAttribLocation(this.program, info.name); 295 | this.attributes[info.name] = loc; 296 | } 297 | }; 298 | 299 | /** 300 | * Creates a uniform setter function that can take dispatch to an array-based 301 | * setter or one with an argument count based on the uniform type. 302 | * @param {function(this:WebGLRenderingContext, ...[number])} set 303 | * @param {function(this:WebGLRenderingContext, (!Array|!ArrayBufferView))} setVec 304 | * @return {function(!Array|!ArrayBufferView|...[number])} 305 | */ 306 | ShaderProgram.createUniformSetter_ = function(set, setVec) { 307 | return function setUniform() { 308 | if (Array.isArray(arguments[0]) || ArrayBuffer.isView(arguments[0])) { 309 | setVec(arguments[0]); 310 | } else { 311 | set.apply(null, arguments); 312 | } 313 | }; 314 | }; 315 | 316 | /** 317 | * Autogenerates setter methods for all active uniforms in this program object. 318 | * When called, previously generated setter methods are discarded. 319 | * @private 320 | * @throws {Error} If uniform of unknown type is found. 321 | */ 322 | ShaderProgram.prototype.initUniforms_ = function() { 323 | var gl = this.gl_; 324 | this.uniforms = {}; 325 | 326 | // loop over current uniforms and create setter functions for them 327 | var count = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS); 328 | for (var i = 0; i < count; i++) { 329 | var info = gl.getActiveUniform(this.program, i); 330 | var name = info.name; 331 | var location = gl.getUniformLocation(this.program, name); 332 | 333 | // float, vec*, or sampler uniforms 334 | if (ShaderProgram.UNIFORM_SETTERS_[info.type]) { 335 | var setterMethod = ShaderProgram.UNIFORM_SETTERS_[info.type]; 336 | var set = gl[setterMethod].bind(gl, location); 337 | var setVec = gl[setterMethod + 'v'].bind(gl, location); 338 | 339 | // base is function that decides on type input what setter to call 340 | // specialized setters are available at fn.set and fn.setVec 341 | // and raw WebGLUniformLocation is on fn.location 342 | this.uniforms[name] = ShaderProgram.createUniformSetter_(set, setVec); 343 | this.uniforms[name].set = set; 344 | this.uniforms[name].setVec = setVec; 345 | this.uniforms[name].location = location; 346 | 347 | // matrix uniform - matrix setters only accept arrays in setter 348 | } else if (ShaderProgram.UNIFORM_MATRIX_SETTERS_[info.type]) { 349 | var setterMatMethod = ShaderProgram.UNIFORM_MATRIX_SETTERS_[info.type]; 350 | 351 | this.uniforms[name] = gl[setterMatMethod].bind(gl, location, false); 352 | this.uniforms[name].location = location; 353 | 354 | } else { 355 | // can't happen unless types are added to spec 356 | throw new Error('Uniform ' + name + ' has unknown type ' + info.type); 357 | } 358 | } 359 | }; 360 | 361 | /** 362 | * Attempts to link the current vertex and fragment shaders. Throws an error if 363 | * the vertex and/or fragment shaders aren't set and compiled, or if the 364 | * shaders fail to link. 365 | * @throws {Error} 366 | */ 367 | ShaderProgram.prototype.link = function() { 368 | if (!this.vertexCompiled_) { 369 | throw new Error('Current vertex shader has not been compiled'); 370 | } 371 | if (!this.fragmentCompiled_) { 372 | throw new Error('Current vertex shader has not been compiled'); 373 | } 374 | 375 | this.gl_.linkProgram(this.program); 376 | 377 | this.programLinked_ = this.gl_.getProgramParameter(this.program, 378 | this.gl_.LINK_STATUS); 379 | 380 | if (!this.programLinked_) { 381 | throw new Error('Program failed to link. Log: ' + this.getProgramInfoLog()); 382 | } else { 383 | this.initAttributes_(); 384 | this.initUniforms_(); 385 | } 386 | }; 387 | 388 | /** 389 | * Installs this program object as part of the current rendering state. Does 390 | * not check program status before attempting; if necessary, use isReady() 391 | * first to check if program has successfully compiled and linked. 392 | */ 393 | ShaderProgram.prototype.use = function() { 394 | this.gl_.useProgram(this.program); 395 | }; 396 | 397 | /** 398 | * Checks to see whether the executables contained in this program can execute 399 | * given the current OpenGL state. The information generated by the validation 400 | * process can be accessed via thisProgram.getProgramInfoLog(). 401 | * This function is typically useful only during application development and 402 | * tends to be quite slow. 403 | * @return {boolean} The validation status. 404 | * @see http://www.khronos.org/opengles/sdk/2.0/docs/man/glValidateProgram.xml 405 | */ 406 | ShaderProgram.prototype.validateProgram = function() { 407 | this.gl_.validateProgram(this.program); 408 | return this.gl_.getProgramParameter(this.program, this.gl_.VALIDATE_STATUS); 409 | }; 410 | --------------------------------------------------------------------------------