├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── aframe-mapbox-component.js └── aframe-mapbox-component.min.js ├── examples ├── basic │ └── index.html ├── custom-style │ └── index.html └── gps │ └── index.html ├── index.html ├── index.js ├── package-lock.json ├── package.json └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.json 2 | /lib/ 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "extends": "airbnb", 6 | "rules": { 7 | "react/prefer-stateless-function": "off", 8 | "react/sort-comp": "off", 9 | "no-param-reassign": "off", 10 | "padded-blocks": "off", 11 | 12 | "react/jsx-filename-extension": ["error", { "extensions": [".js", ".jsx"] }], 13 | "react/prefer-es6-class": ["error", "never"], 14 | "object-curly-spacing": ["error", "never"], 15 | "no-underscore-dangle": ["error", { "allowAfterThis": true }], 16 | "no-unused-vars": ["error", { "argsIgnorePattern": "_" }], 17 | "new-cap": ["error", { "capIsNewExceptions": ["React.Children"] }], 18 | "no-use-before-define": ["error", { "functions": false }] 19 | }, 20 | "parserOptions": { 21 | "ecmaFeatures": { 22 | "experimentalObjectRestSpread": true, 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | cert.pem 3 | key.pem 4 | 5 | .sw[ponm] 6 | examples/build.js 7 | examples/node_modules/ 8 | gh-pages 9 | node_modules/ 10 | npm-debug.log 11 | 12 | # Created by https://www.gitignore.io/api/node 13 | 14 | ### Node ### 15 | # Logs 16 | logs 17 | *.log 18 | npm-debug.log* 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (http://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules 46 | jspm_packages 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | Session.vim 58 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | .sw[ponm] 3 | examples/build.js 4 | examples/node_modules/ 5 | gh-pages 6 | node_modules/ 7 | npm-debug.log 8 | 9 | # Created by https://www.gitignore.io/api/node 10 | 11 | ### Node ### 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (http://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules 43 | jspm_packages 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [Unreleased][] 9 | 10 | ## [2.1.1][] - 2016-11-24 11 | 12 | ### Fixed 13 | 14 | - Only set the center value when it's actually changed 15 | 16 | ## [2.1.0][] - 2016-11-24 17 | 18 | ### Added 19 | 20 | - Fire `map-moveend` event when zoom, center, bearing, or pitch are changed 21 | _after_ the initial render 22 | 23 | ### Fixed 24 | 25 | - Latest mapbox-gl-js dependency which comes with performance enhancements and 26 | bug fixes. 27 | 28 | ## [2.0.6][] - 2016-11-23 29 | 30 | ### Fixed 31 | 32 | - Correct projection between world and pixel coordinates 33 | 34 | ## [2.0.5][] - 2016-11-22 35 | 36 | ### Fixed 37 | 38 | - Changing styles works correctly. 39 | 40 | ## [2.0.4][] - 2016-11-21 41 | 42 | ### Fixed 43 | 44 | - Pixel to world ratio is now correctly calculated 45 | 46 | ## [2.0.3][] - 2016-11-9 47 | 48 | ### Fixed 49 | 50 | - Included missing default `map-style.json` 51 | 52 | ## [2.0.2][] - 2016-10-23 53 | 54 | - Fix changelog enforcement script 55 | 56 | ## [2.0.1][] - 2016-10-23 57 | 58 | - Use changelog enforcement tooling 59 | 60 | ## [2.0.0][] 61 | ### Added 62 | 63 | - Added a Points Of Interest example powered by Foursquare's venues API. 64 | 65 | ### Fixed 66 | 67 | - Fixed a bug where the center would be randomly innacurate 68 | when setting zoom after setting center, 69 | depending on the width & height of the map. 70 | 71 | ## [1.0.0] - 2016-10-18 72 | 73 | Initial release 🎉 74 | 75 | A real-time street map component for 76 | [AframeVR](http://aframe.io) 77 | powered by [MapBox GL](https://github.com/mapbox/mapbox-gl-js) 78 | and [osm2vectortiles](osm2vectortiles.org). 79 | 80 | [Unreleased]: https://github.com/jesstelford/aframe-map/compare/v2.1.1...HEAD 81 | [2.1.1]: https://github.com/jesstelford/aframe-map/compare/v2.1.0...v2.1.1 82 | [2.1.0]: https://github.com/jesstelford/aframe-map/compare/v2.0.6...v2.1.0 83 | [2.0.6]: https://github.com/jesstelford/aframe-map/compare/v2.0.5...v2.0.6 84 | [2.0.5]: https://github.com/jesstelford/aframe-map/compare/v2.0.4...v2.0.5 85 | [2.0.4]: https://github.com/jesstelford/aframe-map/compare/v2.0.3...v2.0.4 86 | [2.0.3]: https://github.com/jesstelford/aframe-map/compare/v2.0.2...v2.0.3 87 | [2.0.2]: https://github.com/jesstelford/aframe-map/compare/v2.0.1...v2.0.2 88 | [2.0.1]: https://github.com/jesstelford/aframe-map/compare/v2.0.0...v2.0.1 89 | [2.0.0]: https://github.com/jesstelford/aframe-map/compare/v1.0.0...v2.0.0 90 | [1.0.0]: https://github.com/jesstelford/aframe-map/tree/v1.0.0 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kevin Ngo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aframe-mapbox-component 2 | 3 | A 3D street map entity & component for [A-Frame](https://aframe.io). Uses [Mapbox version 1.13.0](https://github.com/mapbox/mapbox-gl-js/blob/main/CHANGELOG.md) 3-Clause BSD. 4 | 5 | The component and idea was originally thankfully developed by [jesstelford](https://github.com/jesstelford). I forked and renamed the project accordingly and will continue to develop this component. 6 | 7 | ### Installation 8 | 9 | #### Browser 10 | 11 | Use directly from the unpkg CDN: 12 | 13 | ```html 14 |
15 | 16 | 17 | 18 | 19 | 20 |Basic Satellite Map on a plane.
28 | 29 |Setting a custom style and updating the map position dynamically every 5 secs
33 | 34 |Showing the user's location on a map
38 | 39 | 40 | 41 | 44 | 45 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('mapbox-gl/dist/mapbox-gl.css') 2 | 3 | const extendDeep = AFRAME.utils.extendDeep; 4 | const meshMixin = AFRAME.primitives.getMeshMixin(); 5 | 6 | const cuid = require('cuid'); 7 | 8 | const mapboxgl = require('mapbox-gl'); 9 | 10 | const MAP_LOAD_EVENT = 'mapbox-load'; 11 | const MAP_LOADED_EVENT = 'mapbox-loaded'; 12 | const MAP_MOVE_END_EVENT = 'mapbox-moveend'; 13 | 14 | function parseSpacedFloats (value, count, attributeName) { 15 | if (!value) { 16 | return undefined; 17 | } 18 | 19 | let values = value; 20 | 21 | if (Object.prototype.toString.call(value) === '[object String]') { 22 | values = value.split(','); 23 | } 24 | 25 | if (values.length !== count) { 26 | if (process.env.NODE_ENV !== 'production') { 27 | // eslint-disable-next-line no-console 28 | console.warn( 29 | `Unable to parse value of ${attributeName}: ${value}.` 30 | + ` Expected exactly ${count} space separated floats.` 31 | ); 32 | } 33 | return undefined; 34 | } 35 | 36 | if (values.some(num => isNaN(parseFloat(num)))) { 37 | if (process.env.NODE_ENV !== 'production') { 38 | // eslint-disable-next-line no-console 39 | console.warn( 40 | `Unable to parse value of ${attributeName}: ${value}. ` 41 | + 'Expected values to be floats.' 42 | ); 43 | } 44 | return undefined; 45 | } 46 | 47 | return values; 48 | } 49 | 50 | function setDimensions (id, el, width, height) { 51 | const element = document.querySelector(`#${id}`); 52 | element.style.width = `${width}px`; 53 | element.style.height = `${height}px`; 54 | 55 | AFRAME.utils.entity.setComponentProperty(el, 'material.width', width); 56 | AFRAME.utils.entity.setComponentProperty(el, 'material.height', height); 57 | } 58 | 59 | function getCanvasContainerAssetElement (id, width, height) { 60 | let element = document.querySelector(`#${id}`); 61 | 62 | if (!element) { 63 | element = document.createElement('div'); 64 | } 65 | 66 | element.setAttribute('id', id); 67 | element.style.width = `${width}px`; 68 | element.style.height = `${height}px`; 69 | 70 | // This is necessary because mapbox-gl uses the offsetWidth/Height of the 71 | // container element to calculate the canvas size. But those values are 0 if 72 | // the element (or its parent) are hidden. `position: fixed` means it can be 73 | // calculated correctly. 74 | element.style.position = 'fixed'; 75 | element.style.left = '99999px'; 76 | element.style.top = '0'; 77 | 78 | if (!document.body.contains(element)) { 79 | document.body.appendChild(element); 80 | } 81 | 82 | return element; 83 | } 84 | 85 | function processMapboxCanvasElement (mapboxInstance, canvasContainer) { 86 | const canvas = mapboxInstance.getCanvas(); 87 | canvas.setAttribute('id', cuid()); 88 | canvas.setAttribute('crossorigin', 'anonymous'); 89 | } 90 | 91 | function createMap (canvasId, options) { 92 | return new Promise((resolve, reject) => { 93 | const canvasContainer = getCanvasContainerAssetElement(canvasId, options.width, options.height); 94 | 95 | // eslint-disable-next-line no-new 96 | const mapboxInstance = new mapboxgl.Map(Object.assign({ 97 | container: canvasContainer 98 | }, options)); 99 | 100 | mapboxInstance.on('load', _ => { 101 | mapboxInstance.resize(); 102 | processMapboxCanvasElement(mapboxInstance, canvasContainer); 103 | resolve(mapboxInstance); 104 | }); 105 | }); 106 | } 107 | 108 | /** 109 | * Map component for A-Frame. 110 | */ 111 | AFRAME.registerComponent('mapbox', { 112 | 113 | dependencies: [ 114 | 'geometry', 115 | 'material' 116 | ], 117 | 118 | schema: { 119 | /** 120 | * @param {number} [pxToWorldRatio=100] - The number of pixels per world 121 | * unit to render the map on the plane. ie; when set to 100, will display 122 | * 100 pixels per 1 meter in world space. 123 | */ 124 | pxToWorldRatio: {default: 100}, 125 | 126 | /** 127 | * @param {string} [style=''] - A URL-encoded JSON object of a [MapBox 128 | * style](https://mapbox.com/mapbox-gl-style-spec/). If none is provided, 129 | * a default style will be loaded. 130 | */ 131 | style: {default: ''}, 132 | 133 | /** 134 | * @param {string} [accessToken=''] - Optional access token for styles 135 | * from Mapbox. 136 | */ 137 | accessToken: {default: ''}, 138 | 139 | /** 140 | * @param {int} [minZoom=0] - The minimum zoom level of the map (0-20). (0 141 | * is furthest out) 142 | */ 143 | minZoom: {default: 0}, 144 | 145 | /** 146 | * @param {int} [maxZoom=20] - The maximum zoom level of the map (0-20). (0 147 | * is furthest out) 148 | */ 149 | maxZoom: {default: 20}, 150 | 151 | /** 152 | * @param {int} [bearinSnap=7] - The threshold, measured in degrees, that 153 | * determines when the map's bearing (rotation) will snap to north. For 154 | * example, with a bearingSnap of 7, if the user rotates the map within 7 155 | * degrees of north, the map will automatically snap to exact north. 156 | */ 157 | bearingSnap: {default: 7}, 158 | 159 | /** 160 | * @param {array} [maxBounds=undefined] - If set, the map will be 161 | * constrained to the given bounds. Bounds are specified as 4 space 162 | * delimited floats. The first pair represent the south-west long/lat, the 163 | * second pair represent the north-east long/lat. 164 | */ 165 | maxBounds: { 166 | default: undefined, 167 | type: 'array', 168 | parse: value => { 169 | const values = parseSpacedFloats(value, 4, 'maxBounds'); 170 | 171 | if (!values) { 172 | return undefined; 173 | } 174 | 175 | return [[values[0], values[1]], [values[2], values[3]]]; 176 | } 177 | }, 178 | 179 | /** 180 | * @param {array} [center=[0, 0]] - The inital geographical centerpoint of 181 | * the map in long/lat order. If center is not specified in the 182 | * constructor options, Mapbox GL JS will look for it in the map's style 183 | * object. If it is not specified in the style, either, it will default to 184 | * [0, 0]. Represented as 2 space separated floats. 185 | */ 186 | center: { 187 | default: [0, 0], 188 | type: 'array', 189 | parse: value => { 190 | const values = parseSpacedFloats(value, 2, 'center'); 191 | 192 | if (!values) { 193 | return [0, 0]; 194 | } 195 | 196 | return values; 197 | } 198 | }, 199 | 200 | /** 201 | * @param {int} [zoom=0] - The initial zoom level of the map. If zoom 202 | * is not specified in the constructor options, Mapbox GL JS will look for 203 | * it in the map's style object. If it is not specified in the style, 204 | * either, it will default to 0 . 205 | */ 206 | zoom: {default: 0}, 207 | 208 | /** 209 | * @param {float} [bearing=0] - The initial bearing (rotation) of the map, 210 | * measured in degrees counter-clockwise from north. If bearing is not 211 | * specified in the constructor options, Mapbox GL JS will look for it in 212 | * the map's style object. If it is not specified in the style, either, it 213 | * will default to 0. 214 | */ 215 | bearing: {default: 0.0}, 216 | 217 | /** 218 | * @param {float} [pitch=0] - The initial pitch (tilt) of the map, 219 | * measured in degrees away from the plane of the screen (0-60). If pitch 220 | * is not specified in the constructor options, Mapbox GL JS will look for 221 | * it in the map's style object. If it is not specified in the style, 222 | * either, it will default to 0 . 223 | */ 224 | pitch: {default: 0.0}, 225 | 226 | /** 227 | * All other MapBox GL options are disabled 228 | */ 229 | canvas: {type: 'selector'} 230 | }, 231 | init: function () { 232 | const el = this.el; 233 | const data = this.data; 234 | const geomData = el.components.geometry.data; 235 | 236 | if (data.accessToken) { 237 | mapboxgl.accessToken = data.accessToken; 238 | } 239 | 240 | const style = data.style; 241 | const width = THREE.Math.floorPowerOfTwo(geomData.width * data.pxToWorldRatio); 242 | const height = THREE.Math.floorPowerOfTwo(geomData.height * data.pxToWorldRatio); 243 | this.xPxToWorldRatio = width / geomData.width; 244 | this.yPxToWorldRatio = height / geomData.height; 245 | 246 | const options = Object.assign( 247 | {}, 248 | this.data, 249 | { 250 | style, 251 | width: width, 252 | height: height, 253 | // Required to ensure the canvas can be used as a texture 254 | preserveDrawingBuffer: true, 255 | hash: false, 256 | interactive: false, 257 | attributionControl: false, 258 | scrollZoom: false, 259 | boxZoom: false, 260 | dragRotate: false, 261 | dragPan: false, 262 | keyboard: false, 263 | doubleClickZoom: false, 264 | touchZoomRotate: false, 265 | trackResize: false 266 | } 267 | ); 268 | 269 | this._canvasContainerId = cuid(); 270 | 271 | AFRAME.utils.entity.setComponentProperty(el, 'material.width', width); 272 | AFRAME.utils.entity.setComponentProperty(el, 'material.height', height); 273 | //setDimensions(this._canvasContainerId, el, width, height); 274 | 275 | this.created = false; 276 | 277 | const canvasContainer = getCanvasContainerAssetElement(this._canvasContainerId, options.width, options.height); 278 | 279 | // eslint-disable-next-line no-new 280 | this.mapInstance = new mapboxgl.Map(Object.assign({ 281 | container: canvasContainer 282 | }, options)); 283 | this.el.emit(MAP_LOAD_EVENT); 284 | 285 | this.mapInstance.once('load', _ => { 286 | this.mapInstance.resize(); 287 | processMapboxCanvasElement(this.mapInstance, canvasContainer); 288 | const canvasId = document.querySelector(`#${this._canvasContainerId} canvas`).id; 289 | 290 | this.el.setAttribute('material', 'src', `#${canvasId}`); 291 | this.el.emit(MAP_LOADED_EVENT); 292 | }); 293 | }, 294 | 295 | /** 296 | * Called when component is attached and when component data changes. 297 | * Generally modifies the entity based on the data. 298 | */ 299 | update: function (oldData) { 300 | const data = this.data; 301 | // Everything after this requires a map instance 302 | if (!this.mapInstance) { 303 | return; 304 | } 305 | if (!this.created) { 306 | oldData = {} 307 | this.created = true 308 | } 309 | 310 | // Nothing changed 311 | if (AFRAME.utils.deepEqual(oldData, data)) { 312 | return; 313 | } 314 | 315 | if (oldData.pxToWorldRatio !== data.pxToWorldRatio) { 316 | const geomData = this.el.components.geometry.data; 317 | const width = THREE.Math.floorPowerOfTwo(geomData.width * data.pxToWorldRatio); 318 | const height = THREE.Math.floorPowerOfTwo(geomData.height * data.pxToWorldRatio); 319 | this.xPxToWorldRatio = width / geomData.width; 320 | this.yPxToWorldRatio = height / geomData.height; 321 | setDimensions(this._canvasContainerId, this.el, width, height); 322 | } 323 | 324 | if (oldData.style !== this.data.style) { 325 | const style = this.data.style; 326 | this.mapInstance.setStyle(style); 327 | } 328 | 329 | if (oldData.minZoom !== this.data.minZoom) { 330 | this.mapInstance.setMinZoom(this.data.minZoom); 331 | } 332 | 333 | if (oldData.maxZoom !== this.data.maxZoom) { 334 | this.mapInstance.setMaxZoom(this.data.maxZoom); 335 | } 336 | 337 | if (oldData.maxBounds !== this.data.maxBounds) { 338 | this.mapInstance.setmaxBounds(this.data.maxBounds); 339 | } 340 | 341 | const jumpOptions = {}; 342 | 343 | if (oldData.zoom !== this.data.zoom) { 344 | jumpOptions.zoom = this.data.zoom; 345 | } 346 | 347 | if (!AFRAME.utils.deepEqual(oldData.center, this.data.center)) { 348 | jumpOptions.center = this.data.center; 349 | } 350 | 351 | if (oldData.bearing !== this.data.bearing) { 352 | jumpOptions.bearing = this.data.bearing; 353 | } 354 | 355 | if (oldData.pitch !== this.data.pitch) { 356 | jumpOptions.pitch = this.data.pitch; 357 | } 358 | 359 | if (Object.keys(jumpOptions).length > 0) { 360 | // A way to signal when these async actions have completed 361 | this.mapInstance.once('moveend', _ => { 362 | this.el.emit(MAP_MOVE_END_EVENT); 363 | }); 364 | this.mapInstance.once('idle', _ => { 365 | const material = this.el.getObject3D('mesh').material; 366 | if (material.map) { 367 | material.map.needsUpdate = true; 368 | } 369 | }); 370 | this.mapInstance.jumpTo(jumpOptions); // moveend 371 | } 372 | }, 373 | 374 | /** 375 | * Called when a component is removed (e.g., via removeAttribute). 376 | * Generally undoes all modifications to the entity. 377 | */ 378 | remove () { 379 | // TODO: Kill the map 380 | }, 381 | 382 | /** 383 | * Returns {x, y} representing a position relative to the entity's center, 384 | * that correspond to the specified geographical location. 385 | * 386 | * @param {float} long 387 | * @param {float} lat 388 | */ 389 | project: function (long, lat) { 390 | // The position (origin at top-left corner) in pixel space 391 | const {x: pxX, y: pxY} = this.mapInstance.project([long, lat]); 392 | 393 | // The 3D world size of the entity 394 | const {width: elWidth, height: elHeight} = this.el.components.geometry.data; 395 | 396 | return { 397 | x: (pxX / this.xPxToWorldRatio) - (elWidth / 2), 398 | // y-coord is inverted (positive up in world space, positive down in 399 | // pixel space) 400 | y: -(pxY / this.yPxToWorldRatio) + (elHeight / 2), 401 | z: 0 402 | }; 403 | }, 404 | 405 | unproject: function (x, y) { 406 | // The 3D world size of the entity 407 | const {width: elWidth, height: elHeight} = this.el.components.geometry.data; 408 | 409 | // Converting back to pixel space 410 | const pxX = (x + (elWidth / 2)) * this.xPxToWorldRatio; 411 | // y-coord is inverted (positive up in world space, positive down in 412 | // pixel space) 413 | const pxY = ((elHeight / 2) - y) * this.yPxToWorldRatio; 414 | 415 | // Return the long / lat of that pixel on the map 416 | return this.mapInstance.unproject([pxX, pxY]).toArray(); 417 | }, 418 | 419 | getMap: function() { 420 | return this.mapInstance; 421 | } 422 | }); 423 | 424 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-mapbox-component", 3 | "version": "4.0.1", 4 | "description": "A map component using Mapbox for A-Frame.", 5 | "main": "index.js", 6 | "unpkg": "dist/aframe-mapbox-component.min.js", 7 | "scripts": { 8 | "build": "webpack index.js dist/aframe-mapbox-component.js", 9 | "dev": "budo index.js:dist/aframe-mapbox-component.min.js --port 7000 --live -- -g browserify-css", 10 | "dist": "npm run build && uglifyjs dist/aframe-mapbox-component.js > dist/aframe-mapbox-component.min.js", 11 | "lint": "semistandard -v | snazzy", 12 | "prepublish": "npm run dist", 13 | "ghpages": "ghpages", 14 | "start": "npm run dev", 15 | "test": "karma start ./tests/karma.conf.js", 16 | "test:firefox": "karma start ./tests/karma.conf.js --browsers Firefox", 17 | "test:chrome": "karma start ./tests/karma.conf.js --browsers Chrome" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/mattrei/aframe-mapbox-component.git" 22 | }, 23 | "keywords": [ 24 | "mapbox", 25 | "aframe", 26 | "aframe-component", 27 | "aframe-entity", 28 | "aframe-vr", 29 | "vr", 30 | "mozvr", 31 | "webvr" 32 | ], 33 | "author": "Matthias Treitler