├── .gitignore
├── LICENSE
├── README.md
├── dist
├── aframe-openlayers-component.js
├── aframe-openlayers-component.min.js
└── main.js
├── docs
├── 1.gif
├── 2.gif
├── 3.gif
├── select.png
└── simple.png
├── examples
├── build.js
├── curvedPlane.html
├── dev.js
├── inverted.html
├── invertedTwoMaps.html
├── main.js
├── select.html
└── simple.html
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 |
11 | # compiled output
12 | /tmp
13 | /out-tsc
14 |
15 | # dependencies
16 | /node_modules
17 | /jspm_packages
18 |
19 | # Optional npm cache directory
20 | .npm
21 |
22 | # dotenv environment variables file
23 | .env
24 |
25 | # Coverage directory used by tools like istanbul
26 | coverage
27 |
28 | # Output of 'npm pack'
29 | *.tgz
30 |
31 | # IDEs and editors
32 | /.idea
33 | .project
34 | .classpath
35 | .c9/
36 | *.launch
37 | .settings/
38 | *.sublime-workspace
39 |
40 | # IDE - VSCode
41 | .vscode/*
42 | !.vscode/settings.json
43 | !.vscode/tasks.json
44 | !.vscode/launch.json
45 | !.vscode/extensions.json
46 |
47 | # misc
48 | /.sass-cache
49 | /connect.lock
50 | /coverage
51 | /libpeerconnection.log
52 | npm-debug.log
53 | testem.log
54 | /typings
55 |
56 | # e2e
57 | /e2e/*.js
58 | /e2e/*.map
59 |
60 | # System Files
61 | .DS_Store
62 | Thumbs.db
63 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Luis Calisto
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # aframe-openlayers-component
2 |
3 | An A-Frame component that allows the use of OpenLayers maps inside A-Frame.
4 |
5 | This component allows the use of any OpenLayers map into any A-Frame object. It uses OpenLayers `postcompose` map event to export the map into an image (using canvas) and then the image is added into the A-Frame material. Also, it tries to pass OpenLayers interactions into A-frame to allow selection, pan, move etc. inside the VR environment.
6 |
7 | **[Simple example:](https://lcalisto.github.io/aframe-openlayers-component/examples/simple.html)**
8 |
9 | [](https://lcalisto.github.io/aframe-openlayers-component/examples/simple.html)
10 |
11 |
12 | **[Inverted globe example with country selection and minimap.](https://lcalisto.github.io/aframe-openlayers-component/examples/invertedTwoMaps.html)**
13 |
14 | [](https://lcalisto.github.io/aframe-openlayers-component/examples/invertedTwoMaps.html)
15 |
16 | [More examples ...](#examples)
17 |
18 | ### Schema
19 |
20 | | Property | Description | Default Value | Required |
21 | | -------- | ----------- | ------------- | ---- |
22 | | **map** | Javascript variable name of the OpenLayers map to use. | map | true|
23 | | **pixToVRRatio** | The multiplication factor between meters in A-Frame and the pixels of the map. ie; when set to 100, will display 100 pixels per 1 meter in VR space. Please check [Info about map](#info-about-map) | 100 | true |
24 | | **OlEvent** | OpenLayers event used in the interaction. This event will be passed into aframe. ie; when select is active probably you want to pass the click event. [More info about OpenLayers interaction events.](https://openlayers.org/en/latest/apidoc/ol.interaction.html) | click | false |
25 | | **aframeEvent** | A-Frame event that will be the destiny for the OLEvent. ie; When select interaction is active probably you want to pass also the click event. [More info about A-Frame events.](https://aframe.io/docs/0.8.0/introduction/interactions-and-controllers.html#sidebar) | click | false |
26 | | **width** | Normally map width is computed based on component width. But in some cases you might not give width in the component, ie; when using a radius instead of width and height. In this cases, if width is not provided in the component, you need to provide a width for the map in VR units (meters). Please check [Info about map](#info-about-map) | | false |
27 | | **height** | Normally map height is computed based on component height. But in some cases you might not give height in the component, ie; when using a radius instead of width and height. In this cases, if height is not provided in the component, you need to provide a height for the map in VR units (meters). Please check [Info about map](#info-about-map) | | false |
28 |
29 | ### Info about map
30 |
31 | Map size is computed using the folowing formula: MapWidth = (pixToVRRatio * component width) and MapHeight = (pixToVRRatio * component height)
32 |
33 | The higher `pixToVRRatio`, the more map area will be displayed per VR unit. The canvas has to be translated into a plane in VR space. This is combined with the width and height in VR space (from geometry.width and geometry.height on the entity or in case they don't exist then from the provided width and height in the OL object) to set up the map plane for rendering in 3D.
34 |
35 | The map is rendered as a texture on a 3D plane. For best performance, texture sizes should be kept to powers of 2, because of that, the component automatically resizes geometry.width and/or geometry.height to the closest power of 2 using the provided pixToVRRatio. If you don't want the component to automatically resize your objects you should make sure `width * pixToVRRatio` and `height * pixToVRRatio` are powers of 2.
36 |
37 | ### Installation options:
38 |
39 |
40 | #### 1. npm
41 |
42 | Via npm:
43 |
44 | ```bash
45 | npm install aframe
46 | npm install aframe-openlayers-component
47 | ```
48 |
49 | Then
50 |
51 | ```js
52 | require('aframe');
53 | require('aframe-openlayers-component');
54 | ```
55 |
56 | #### 2. Directly including the [minified file](dist)
57 |
58 | ```html
59 |
60 |
61 | A-Frame Openlayers Component - Simple example
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
116 |
117 |
118 |
119 | ```
120 |
121 | ### Examples
122 |
123 | All examples are in [examples/](examples/) folder.
124 |
125 | 1. [Simple plane and a globe using the same map.](https://lcalisto.github.io/aframe-openlayers-component/examples/simple.html)
126 |
127 | 2. [Curved plane.](https://lcalisto.github.io/aframe-openlayers-component/examples/curvedPlane.html)
128 |
129 | 3. [Two map panels. A big one allowing selection and a small with the selected country.](https://lcalisto.github.io/aframe-openlayers-component/examples/select.html)
130 |
131 | 4. [Inverted globe with country selection.](https://lcalisto.github.io/aframe-openlayers-component/examples/inverted.html)
132 |
133 | 5. [Inverted globe with country selection and a centered mini map.](https://lcalisto.github.io/aframe-openlayers-component/examples/invertedTwoMaps.html)
134 |
135 |
136 |
137 |
138 | ### Notes
139 |
140 | * Component based on the idea of Allan Walker from [https://blog.mapbox.com/mapbox-gl-js-ar-js-a-frame-vr-mapbox-ar-vr-93c09be08742](https://blog.mapbox.com/mapbox-gl-js-ar-js-a-frame-vr-mapbox-ar-vr-93c09be08742)
141 |
142 | * API had some influences from the [A-Frame Tangram-Component](https://github.com/mattrei/aframe-tangram-component)
143 |
--------------------------------------------------------------------------------
/dist/aframe-openlayers-component.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, {
40 | /******/ configurable: false,
41 | /******/ enumerable: true,
42 | /******/ get: getter
43 | /******/ });
44 | /******/ }
45 | /******/ };
46 | /******/
47 | /******/ // getDefaultExport function for compatibility with non-harmony modules
48 | /******/ __webpack_require__.n = function(module) {
49 | /******/ var getter = module && module.__esModule ?
50 | /******/ function getDefault() { return module['default']; } :
51 | /******/ function getModuleExports() { return module; };
52 | /******/ __webpack_require__.d(getter, 'a', getter);
53 | /******/ return getter;
54 | /******/ };
55 | /******/
56 | /******/ // Object.prototype.hasOwnProperty.call
57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58 | /******/
59 | /******/ // __webpack_public_path__
60 | /******/ __webpack_require__.p = "";
61 | /******/
62 | /******/ // Load entry module and return exports
63 | /******/ return __webpack_require__(__webpack_require__.s = 0);
64 | /******/ })
65 | /************************************************************************/
66 | /******/ ([
67 | /* 0 */
68 | /***/ (function(module, exports) {
69 |
70 | if (typeof AFRAME === 'undefined') {
71 | throw new Error('Component attempted to register before AFRAME was available.');
72 | }
73 |
74 | AFRAME.registerComponent('ol', {
75 | schema: {
76 | map: {
77 | type: 'string',
78 | default: 'map'
79 | },
80 | pixToVRRatio: {
81 | default: 100
82 | },
83 | aframeEvent: {
84 | type: 'string',
85 | default: 'click'
86 | },
87 | OlEvent: {
88 | type: 'string',
89 | default: 'click'
90 | }
91 | },
92 | init: function () {
93 | },
94 | update: function (oldData) {
95 | var data = this.data; // Component property values.
96 | var el = this.el; // Reference to the component's entity.
97 | // Nothing changed
98 | if (AFRAME.utils.deepEqual(oldData, data)) {
99 | return;
100 | }
101 | //Check for the required schema elements
102 | if(data.map==''){
103 | console.warn('OpenLayers component: OpenLayers map variable not specified. Aborting!');
104 | return;
105 | }
106 | if(data.pixToVRRatio==0){
107 | console.warn('OpenLayers component: pixToVRRatio not specified. Aborting!');
108 | return;
109 | }
110 | if(data.aframeEvent==''){
111 | console.warn('OpenLayers component: AFRAME event not specified. Aborting!');
112 | return;
113 | }
114 | if(data.OlEvent==''){
115 | console.warn('OpenLayers component: OpenLayers interaction event not specified. Aborting!');
116 | return;
117 | }
118 | var olMap=eval(data.map)
119 | if(typeof(olMap)!='object'){
120 | console.warn('OpenLayers component: Map object/variable cannot be found. Aborting!');
121 | return;
122 | }
123 | //Check if element has height and width. Otherwise is not possible to plot the map on it.
124 | if(el.getAttribute('height')==null){
125 | console.warn('OpenLayers component: Element Height is not specified. Aborting!');
126 | return;
127 | }
128 | if(el.getAttribute('width')==null){
129 | console.warn('OpenLayers component: Element Width is not specified. Aborting!');
130 | return;
131 | }
132 |
133 | // Set pixToVRRatio
134 | // from https://github.com/jesstelford/aframe-map -----> https://github.com/jesstelford/aframe-map/blob/master/src/component.js
135 |
136 | var pixToVRRatio=data.pixToVRRatio;
137 | var compWidth=el.getAttribute('width');
138 | var compHeight=el.getAttribute('height');
139 | var mapWidth = compWidth*pixToVRRatio;
140 | var mapHeight = compHeight*pixToVRRatio;
141 |
142 | ///////////////////////////
143 | // Check if mapWidth is a power of 2.
144 | //
145 | // If not a new component size will be calculated and set using the provided ratio.
146 | //
147 | // More info here: https://gamedev.stackexchange.com/questions/26187/why-are-textures-always-square-powers-of-two-what-if-they-arent
148 | ///////////////////////////
149 |
150 | if (!(Math.log(mapWidth)/Math.log(2)) % 1 === 0) {
151 | //throw new Error("Map height is not power of 2! -> "+ mapHeight);
152 | var newMapWidth=this.nearestPow2(mapWidth);
153 | var newWidth=this.calculateNewSize(newMapWidth,pixToVRRatio);
154 | console.warn('Map width ',mapWidth,' (Ratio * component width - ',pixToVRRatio,'*',compWidth,') is not a power of 2. Setting new map width to ',newMapWidth,' and adjusting component width to ',newWidth);
155 | el.setAttribute('width',newWidth);
156 | mapWidth=newMapWidth;
157 | }
158 | if (!(Math.log(mapHeight)/Math.log(2)) % 1 === 0) {
159 | //throw new Error("Map height is not power of 2! -> "+mapHeight);
160 | var newMapHeight=this.nearestPow2(mapHeight)
161 | var newHeight=this.calculateNewSize(newMapHeight,pixToVRRatio);
162 | console.warn('Map height ',mapHeight,' (Ratio * component height - ',pixToVRRatio,'*',compHeight,') is not a power of 2. Setting new map height to ',newMapHeight,' and adjusting component height to ',newHeight);
163 | el.setAttribute('height',newHeight);
164 | mapHeight=newMapHeight;
165 |
166 | }
167 |
168 | ////Set Map Size
169 |
170 | olMap.setSize([mapWidth,mapHeight]);
171 |
172 | //// Set A-frame events
173 |
174 | el.addEventListener(data.aframeEvent, function(obj) {
175 | var objX=obj.detail.intersection.uv.x;
176 | var objY=obj.detail.intersection.uv.y;
177 |
178 | var mapSizeX=olMap.getSize()[0];
179 | var mapSizeY=olMap.getSize()[1];
180 |
181 | var objMapX=objX*mapSizeX;
182 | var objMapY=(1-objY)*mapSizeY; // in Y needs to be reversed because map origin is top left
183 | this.simulateEvent(data.OlEvent,objMapX,objMapY,false,olMap);
184 | });
185 |
186 | // Postcompose event. This were we convert canvas to image and the assign this image to the aframe object
187 |
188 | olMap.on('postcompose', function(event) {
189 | var canvas = event.context.canvas;
190 | var img = canvas.toDataURL("image/png")
191 | el.setAttribute("src", img);
192 | });
193 | olMap.renderSync();
194 | },
195 |
196 | //Simulated event function based on https://github.com/openlayers/openlayers/blob/83f87a1f1ee4d9a2e1e3954c908188c8a73cfb75/test/spec/ol/interaction/draw.test.js#L68
197 | simulateEvent:function (type,x,y,shiftKey,olMap){
198 | var event = new ol.pointer.PointerEvent(type, {
199 | clientX: x,
200 | clientY: y,
201 | shiftKey: shiftKey
202 | });
203 | olMap.handleMapBrowserEvent(new ol.MapBrowserPointerEvent(type, olMap, event)); //new ol.MapBrowserPointerEvent is available in openlayers debug
204 | },
205 | nearestPow2: function( aSize ){
206 | return Math.pow( 2, Math.round( Math.log( aSize ) / Math.log( 2 ) ) );
207 | },
208 |
209 | calculateNewSize: function (newMapSize, ratio ){
210 | return newMapSize/ratio;
211 | }
212 | });
213 |
214 | /***/ })
215 | /******/ ]);
--------------------------------------------------------------------------------
/dist/aframe-openlayers-component.min.js:
--------------------------------------------------------------------------------
1 | !function(t){function e(i){if(n[i])return n[i].exports;var o=n[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};e.m=t,e.c=n,e.d=function(t,n,i){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:i})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=0)}([function(module,exports){if("undefined"==typeof AFRAME)throw new Error("Component attempted to register before AFRAME was available.");AFRAME.registerComponent("ol",{schema:{map:{type:"string",default:"map"},pixToVRRatio:{default:100},aframeEvent:{type:"string",default:"click"},OlEvent:{type:"string",default:"click"}},init:function(){},update:function(oldData){var data=this.data,el=this.el;if(!AFRAME.utils.deepEqual(oldData,data)){if(""==data.map)return void console.warn("OpenLayers component: OpenLayers map variable not specified. Aborting!");if(0==data.pixToVRRatio)return void console.warn("OpenLayers component: pixToVRRatio not specified. Aborting!");if(""==data.aframeEvent)return void console.warn("OpenLayers component: AFRAME event not specified. Aborting!");if(""==data.OlEvent)return void console.warn("OpenLayers component: OpenLayers interaction event not specified. Aborting!");var olMap=eval(data.map);if("object"!=typeof olMap)return void console.warn("OpenLayers component: Map object/variable cannot be found. Aborting!");if(null==el.getAttribute("height"))return void console.warn("OpenLayers component: Element Height is not specified. Aborting!");if(null==el.getAttribute("width"))return void console.warn("OpenLayers component: Element Width is not specified. Aborting!");var pixToVRRatio=data.pixToVRRatio,compWidth=el.getAttribute("width"),compHeight=el.getAttribute("height"),mapWidth=compWidth*pixToVRRatio,mapHeight=compHeight*pixToVRRatio;if(!(Math.log(mapWidth)/Math.log(2))%1==0){var newMapWidth=this.nearestPow2(mapWidth),newWidth=this.calculateNewSize(newMapWidth,pixToVRRatio);console.warn("Map width ",mapWidth," (Ratio * component width - ",pixToVRRatio,"*",compWidth,") is not a power of 2. Setting new map width to ",newMapWidth," and adjusting component width to ",newWidth),el.setAttribute("width",newWidth),mapWidth=newMapWidth}if(!(Math.log(mapHeight)/Math.log(2))%1==0){var newMapHeight=this.nearestPow2(mapHeight),newHeight=this.calculateNewSize(newMapHeight,pixToVRRatio);console.warn("Map height ",mapHeight," (Ratio * component height - ",pixToVRRatio,"*",compHeight,") is not a power of 2. Setting new map height to ",newMapHeight," and adjusting component height to ",newHeight),el.setAttribute("height",newHeight),mapHeight=newMapHeight}olMap.setSize([mapWidth,mapHeight]),el.addEventListener(data.aframeEvent,function(t){var e=t.detail.intersection.uv.x,n=t.detail.intersection.uv.y,i=olMap.getSize()[0],o=olMap.getSize()[1],a=e*i,r=(1-n)*o;this.simulateEvent(data.OlEvent,a,r,!1,olMap)}),olMap.on("postcompose",function(t){var e=t.context.canvas,n=e.toDataURL("image/png");el.setAttribute("src",n)}),olMap.renderSync()}},simulateEvent:function(t,e,n,i,o){var a=new ol.pointer.PointerEvent(t,{clientX:e,clientY:n,shiftKey:i});o.handleMapBrowserEvent(new ol.MapBrowserPointerEvent(t,o,a))},nearestPow2:function(t){return Math.pow(2,Math.round(Math.log(t)/Math.log(2)))},calculateNewSize:function(t,e){return t/e}})}]);
--------------------------------------------------------------------------------
/dist/main.js:
--------------------------------------------------------------------------------
1 | !function(t){var e={};function n(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(i,o,function(e){return t[e]}.bind(null,o));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=2)}([function(module,exports){!function(t){function e(i){if(n[i])return n[i].exports;var o=n[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};e.m=t,e.c=n,e.d=function(t,n,i){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:i})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=0)}([function(module,exports){if("undefined"==typeof AFRAME)throw new Error("Component attempted to register before AFRAME was available.");AFRAME.registerComponent("ol",{schema:{map:{type:"string",default:"map"},pixToVRRatio:{default:100},aframeEvent:{type:"string",default:"click"},OlEvent:{type:"string",default:"click"}},init:function(){},update:function(oldData){var data=this.data,el=this.el;if(!AFRAME.utils.deepEqual(oldData,data)){if(""==data.map)return void console.warn("OpenLayers component: OpenLayers map variable not specified. Aborting!");if(0==data.pixToVRRatio)return void console.warn("OpenLayers component: pixToVRRatio not specified. Aborting!");if(""==data.aframeEvent)return void console.warn("OpenLayers component: AFRAME event not specified. Aborting!");if(""==data.OlEvent)return void console.warn("OpenLayers component: OpenLayers interaction event not specified. Aborting!");var olMap=eval(data.map);if("object"!=typeof olMap)return void console.warn("OpenLayers component: Map object/variable cannot be found. Aborting!");if(null==el.getAttribute("height"))return void console.warn("OpenLayers component: Element Height is not specified. Aborting!");if(null==el.getAttribute("width"))return void console.warn("OpenLayers component: Element Width is not specified. Aborting!");var pixToVRRatio=data.pixToVRRatio,compWidth=el.getAttribute("width"),compHeight=el.getAttribute("height"),mapWidth=compWidth*pixToVRRatio,mapHeight=compHeight*pixToVRRatio;if(!(Math.log(mapWidth)/Math.log(2))%1==0){var newMapWidth=this.nearestPow2(mapWidth),newWidth=this.calculateNewSize(newMapWidth,pixToVRRatio);console.warn("Map width ",mapWidth," (Ratio * component width - ",pixToVRRatio,"*",compWidth,") is not a power of 2. Setting new map width to ",newMapWidth," and adjusting component width to ",newWidth),el.setAttribute("width",newWidth),mapWidth=newMapWidth}if(!(Math.log(mapHeight)/Math.log(2))%1==0){var newMapHeight=this.nearestPow2(mapHeight),newHeight=this.calculateNewSize(newMapHeight,pixToVRRatio);console.warn("Map height ",mapHeight," (Ratio * component height - ",pixToVRRatio,"*",compHeight,") is not a power of 2. Setting new map height to ",newMapHeight," and adjusting component height to ",newHeight),el.setAttribute("height",newHeight),mapHeight=newMapHeight}olMap.setSize([mapWidth,mapHeight]),el.addEventListener(data.aframeEvent,function(t){var e=t.detail.intersection.uv.x,n=t.detail.intersection.uv.y,i=e*olMap.getSize()[0],o=(1-n)*olMap.getSize()[1];this.simulateEvent(data.OlEvent,i,o,!1,olMap)}),olMap.on("postcompose",function(t){var e=t.context.canvas.toDataURL("image/png");el.setAttribute("src",e)}),olMap.renderSync()}},simulateEvent:function(t,e,n,i,o){var a=new ol.pointer.PointerEvent(t,{clientX:e,clientY:n,shiftKey:i});o.handleMapBrowserEvent(new ol.MapBrowserPointerEvent(t,o,a))},nearestPow2:function(t){return Math.pow(2,Math.round(Math.log(t)/Math.log(2)))},calculateNewSize:function(t,e){return t/e}})}])},function(module,exports){if("undefined"==typeof AFRAME)throw new Error("Component attempted to register before AFRAME was available.");AFRAME.registerComponent("ol",{schema:{map:{type:"string",default:"map"},height:{type:"number"},width:{type:"number"},pixToVRRatio:{default:100},aframeEvent:{type:"string",default:"click"},OlEvent:{type:"string",default:"click"}},init:function(){},update:function(oldData){var data=this.data,el=this.el,comp=this;if(!AFRAME.utils.deepEqual(oldData,data))if(""!=data.map)if(0!=data.pixToVRRatio)if(""!=data.aframeEvent)if(""!=data.OlEvent){var olMap=eval(data.map);if("object"==typeof olMap)if(null!=el.getAttribute("height")||null!=data.height&&0!=data.height)if(null!=el.getAttribute("width")||null!=data.width&&0!=data.width){var pixToVRRatio=data.pixToVRRatio,compHeight=null==el.getAttribute("height")?data.height:el.getAttribute("height"),compWidth=null==el.getAttribute("width")?data.width:el.getAttribute("width"),mapWidth=compWidth*pixToVRRatio,mapHeight=compHeight*pixToVRRatio;if(!(Math.log(mapWidth)/Math.log(2))%1==0){var newMapWidth=this.nearestPow2(mapWidth),newWidth=this.calculateNewSize(newMapWidth,pixToVRRatio);console.warn("Map width ",mapWidth," (Ratio * component width - ",pixToVRRatio,"*",compWidth,") is not a power of 2. Setting new map width to ",newMapWidth," and adjusting component width to ",newWidth),null==el.getAttribute("width")?data.width=newWidth:el.setAttribute("width",newWidth),mapWidth=newMapWidth}if(!(Math.log(mapHeight)/Math.log(2))%1==0){var newMapHeight=this.nearestPow2(mapHeight),newHeight=this.calculateNewSize(newMapHeight,pixToVRRatio);console.warn("Map height ",mapHeight," (Ratio * component height - ",pixToVRRatio,"*",compHeight,") is not a power of 2. Setting new map height to ",newMapHeight," and adjusting component height to ",newHeight),null==el.getAttribute("height")?data.height=newHeight:el.setAttribute("height",newHeight),mapHeight=newMapHeight}olMap.setSize([mapWidth,mapHeight]),el.addEventListener(data.aframeEvent,function(t){var e=t.detail.intersection.uv.x,n=t.detail.intersection.uv.y,i=e*olMap.getSize()[0],o=(1-n)*olMap.getSize()[1];comp.simulateEvent(data.OlEvent,i,o,!1,olMap)}),olMap.on("postcompose",function(t){var e=t.context.canvas.toDataURL("image/png");el.setAttribute("material","src",e)}),olMap.renderSync()}else console.warn("OpenLayers component: Element Width is not specified. Aborting!");else console.warn("OpenLayers component: Element Height is not specified. Aborting!");else console.warn("OpenLayers component: Map object/variable cannot be found. Aborting!")}else console.warn("OpenLayers component: OpenLayers interaction event not specified. Aborting!");else console.warn("OpenLayers component: AFRAME event not specified. Aborting!");else console.warn("OpenLayers component: pixToVRRatio not specified. Aborting!");else console.warn("OpenLayers component: OpenLayers map variable not specified. Aborting!")},simulateEvent:function(t,e,n,i,o){var a=new ol.pointer.PointerEvent(t,{clientX:e,clientY:n,shiftKey:i});o.handleMapBrowserEvent(new ol.MapBrowserPointerEvent(t,o,a))},nearestPow2:function(t){return Math.pow(2,Math.round(Math.log(t)/Math.log(2)))},calculateNewSize:function(t,e){return t/e}})},function(t,e,n){n(1),t.exports=n(0)}]);
--------------------------------------------------------------------------------
/docs/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcalisto/aframe-openlayers-component/c45dcf471c61046e144f618ea29b24403a576c9d/docs/1.gif
--------------------------------------------------------------------------------
/docs/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcalisto/aframe-openlayers-component/c45dcf471c61046e144f618ea29b24403a576c9d/docs/2.gif
--------------------------------------------------------------------------------
/docs/3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcalisto/aframe-openlayers-component/c45dcf471c61046e144f618ea29b24403a576c9d/docs/3.gif
--------------------------------------------------------------------------------
/docs/select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcalisto/aframe-openlayers-component/c45dcf471c61046e144f618ea29b24403a576c9d/docs/select.png
--------------------------------------------------------------------------------
/docs/simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcalisto/aframe-openlayers-component/c45dcf471c61046e144f618ea29b24403a576c9d/docs/simple.png
--------------------------------------------------------------------------------
/examples/build.js:
--------------------------------------------------------------------------------
1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i https://github.com/jesstelford/aframe-map/blob/master/src/component.js
76 |
77 | var pixToVRRatio=data.pixToVRRatio;
78 |
79 | //var compHeight=el.getAttribute('height');
80 | var compHeight = el.getAttribute('height')==null ? data.height : el.getAttribute('height');
81 |
82 | //var compWidth=el.getAttribute('width');
83 | var compWidth = el.getAttribute('width')==null ? data.width : el.getAttribute('width');
84 |
85 | var mapWidth = compWidth*pixToVRRatio;
86 | var mapHeight = compHeight*pixToVRRatio;
87 |
88 | ///////////////////////////
89 | // Check if mapWidth is a power of 2.
90 | //
91 | // If not a new component size will be calculated and set using the provided ratio.
92 | //
93 | // More info here: https://gamedev.stackexchange.com/questions/26187/why-are-textures-always-square-powers-of-two-what-if-they-arent
94 | ///////////////////////////
95 |
96 | if (!(Math.log(mapWidth)/Math.log(2)) % 1 === 0) {
97 | //throw new Error("Map height is not power of 2! -> "+ mapHeight);
98 | var newMapWidth=this.nearestPow2(mapWidth);
99 | var newWidth=this.calculateNewSize(newMapWidth,pixToVRRatio);
100 | console.warn('Map width ',mapWidth,' (Ratio * component width - ',pixToVRRatio,'*',compWidth,') is not a power of 2. Setting new map width to ',newMapWidth,' and adjusting component width to ',newWidth);
101 | if (el.getAttribute('width')==null ){
102 | data.width=newWidth;
103 | }else{
104 | el.setAttribute('width',newWidth);
105 | }
106 | mapWidth=newMapWidth;
107 | }
108 | if (!(Math.log(mapHeight)/Math.log(2)) % 1 === 0) {
109 | //throw new Error("Map height is not power of 2! -> "+mapHeight);
110 | var newMapHeight=this.nearestPow2(mapHeight)
111 | var newHeight=this.calculateNewSize(newMapHeight,pixToVRRatio);
112 | console.warn('Map height ',mapHeight,' (Ratio * component height - ',pixToVRRatio,'*',compHeight,') is not a power of 2. Setting new map height to ',newMapHeight,' and adjusting component height to ',newHeight);
113 | if (el.getAttribute('height')==null ){
114 | data.height=newHeight;
115 | }else{
116 | el.setAttribute('height',newHeight);
117 | }
118 | mapHeight=newMapHeight;
119 |
120 | }
121 |
122 | ////Set Map Size
123 |
124 | olMap.setSize([mapWidth,mapHeight]);
125 |
126 | //// Set A-frame events
127 |
128 | el.addEventListener(data.aframeEvent, function(obj) {
129 | var objX=obj.detail.intersection.uv.x;
130 | var objY=obj.detail.intersection.uv.y;
131 |
132 | var mapSizeX=olMap.getSize()[0];
133 | var mapSizeY=olMap.getSize()[1];
134 |
135 | var objMapX=objX*mapSizeX;
136 | var objMapY=(1-objY)*mapSizeY; // in Y needs to be reversed because map origin is top left
137 | comp.simulateEvent(data.OlEvent,objMapX,objMapY,false,olMap);
138 | });
139 |
140 | // Postcompose event. This were we convert canvas to image and the assign this image to the aframe object
141 |
142 | olMap.on('postcompose', function(event) {
143 | var canvas = event.context.canvas;
144 | var img = canvas.toDataURL("image/png")
145 | //el.setAttribute("src", img);
146 | el.setAttribute("material","src", img);
147 | });
148 | olMap.renderSync();
149 | },
150 |
151 | //Simulated event function based on https://github.com/openlayers/openlayers/blob/83f87a1f1ee4d9a2e1e3954c908188c8a73cfb75/test/spec/ol/interaction/draw.test.js#L68
152 | simulateEvent:function (type,x,y,shiftKey,olMap){
153 | var event = new ol.pointer.PointerEvent(type, {
154 | clientX: x,
155 | clientY: y,
156 | shiftKey: shiftKey
157 | });
158 | olMap.handleMapBrowserEvent(new ol.MapBrowserPointerEvent(type, olMap, event)); //new ol.MapBrowserPointerEvent is available in openlayers debug
159 | },
160 | nearestPow2: function( aSize ){
161 | return Math.pow( 2, Math.round( Math.log( aSize ) / Math.log( 2 ) ) );
162 | },
163 |
164 | calculateNewSize: function (newMapSize, ratio ){
165 | return newMapSize/ratio;
166 | }
167 | });
168 | },{}]},{},[1]);
169 |
--------------------------------------------------------------------------------
/examples/curvedPlane.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OL VR
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/examples/dev.js:
--------------------------------------------------------------------------------
1 | // This file is only required if you use npm for dev purposes. Check dev script in ../package.json for more details
2 | var serveIndex = require('serve-index')
3 |
4 | require('budo').cli(process.argv.slice(2), {
5 | middleware: serveIndex(__dirname)
6 | })
7 |
--------------------------------------------------------------------------------
/examples/inverted.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OL VR
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/examples/invertedTwoMaps.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OL VR
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/examples/main.js:
--------------------------------------------------------------------------------
1 | require('../index.js');
--------------------------------------------------------------------------------
/examples/select.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OL VR
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
103 |
104 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/examples/simple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OL VR
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | if (typeof AFRAME === 'undefined') {
2 | throw new Error('Component attempted to register before AFRAME was available.');
3 | }
4 |
5 | AFRAME.registerComponent('ol', {
6 | schema: {
7 | map: {
8 | type: 'string',
9 | default: 'map'
10 | },
11 | height: {
12 | type: 'number'
13 | },
14 | width: {
15 | type: 'number'
16 | },
17 | pixToVRRatio: {
18 | default: 100
19 | },
20 | aframeEvent: {
21 | type: 'string',
22 | default: 'click'
23 | },
24 | OlEvent: {
25 | type: 'string',
26 | default: 'click'
27 | }
28 | },
29 | init: function () {
30 | },
31 | update: function (oldData) {
32 | var data = this.data; // Component property values.
33 | var el = this.el; // Reference to the component's entity.
34 | var comp=this;
35 | // Nothing changed
36 | if (AFRAME.utils.deepEqual(oldData, data)) {
37 | return;
38 | }
39 | //Check for the required schema elements
40 | if(data.map==''){
41 | console.warn('OpenLayers component: OpenLayers map variable not specified. Aborting!');
42 | return;
43 | }
44 | if(data.pixToVRRatio==0){
45 | console.warn('OpenLayers component: pixToVRRatio not specified. Aborting!');
46 | return;
47 | }
48 | if(data.aframeEvent==''){
49 | console.warn('OpenLayers component: AFRAME event not specified. Aborting!');
50 | return;
51 | }
52 | if(data.OlEvent==''){
53 | console.warn('OpenLayers component: OpenLayers interaction event not specified. Aborting!');
54 | return;
55 | }
56 | var olMap=eval(data.map)
57 | if(typeof(olMap)!='object'){
58 | console.warn('OpenLayers component: Map object/variable cannot be found. Aborting!');
59 | return;
60 | }
61 | //Check if element has height and width. Otherwise is not possible to plot the map on it.
62 | if(el.getAttribute('height')==null && (data.height==null || data.height==0)){
63 | console.warn('OpenLayers component: Element Height is not specified. Aborting!');
64 | return;
65 | }
66 | if(el.getAttribute('width')==null && (data.width==null || data.width==0)){
67 | console.warn('OpenLayers component: Element Width is not specified. Aborting!');
68 | return;
69 | }
70 |
71 | // Set pixToVRRatio
72 | // from https://github.com/jesstelford/aframe-map -----> https://github.com/jesstelford/aframe-map/blob/master/src/component.js
73 |
74 | var pixToVRRatio=data.pixToVRRatio;
75 |
76 | //var compHeight=el.getAttribute('height');
77 | var compHeight = el.getAttribute('height')==null ? data.height : el.getAttribute('height');
78 |
79 | //var compWidth=el.getAttribute('width');
80 | var compWidth = el.getAttribute('width')==null ? data.width : el.getAttribute('width');
81 |
82 | var mapWidth = compWidth*pixToVRRatio;
83 | var mapHeight = compHeight*pixToVRRatio;
84 |
85 | ///////////////////////////
86 | // Check if mapWidth is a power of 2.
87 | //
88 | // If not a new component size will be calculated and set using the provided ratio.
89 | //
90 | // More info here: https://gamedev.stackexchange.com/questions/26187/why-are-textures-always-square-powers-of-two-what-if-they-arent
91 | ///////////////////////////
92 |
93 | if (!(Math.log(mapWidth)/Math.log(2)) % 1 === 0) {
94 | //throw new Error("Map height is not power of 2! -> "+ mapHeight);
95 | var newMapWidth=this.nearestPow2(mapWidth);
96 | var newWidth=this.calculateNewSize(newMapWidth,pixToVRRatio);
97 | console.warn('Map width ',mapWidth,' (Ratio * component width - ',pixToVRRatio,'*',compWidth,') is not a power of 2. Setting new map width to ',newMapWidth,' and adjusting component width to ',newWidth);
98 | if (el.getAttribute('width')==null ){
99 | data.width=newWidth;
100 | }else{
101 | el.setAttribute('width',newWidth);
102 | }
103 | mapWidth=newMapWidth;
104 | }
105 | if (!(Math.log(mapHeight)/Math.log(2)) % 1 === 0) {
106 | //throw new Error("Map height is not power of 2! -> "+mapHeight);
107 | var newMapHeight=this.nearestPow2(mapHeight)
108 | var newHeight=this.calculateNewSize(newMapHeight,pixToVRRatio);
109 | console.warn('Map height ',mapHeight,' (Ratio * component height - ',pixToVRRatio,'*',compHeight,') is not a power of 2. Setting new map height to ',newMapHeight,' and adjusting component height to ',newHeight);
110 | if (el.getAttribute('height')==null ){
111 | data.height=newHeight;
112 | }else{
113 | el.setAttribute('height',newHeight);
114 | }
115 | mapHeight=newMapHeight;
116 |
117 | }
118 |
119 | ////Set Map Size
120 |
121 | olMap.setSize([mapWidth,mapHeight]);
122 |
123 | //// Set A-frame events
124 |
125 | el.addEventListener(data.aframeEvent, function(obj) {
126 | var objX=obj.detail.intersection.uv.x;
127 | var objY=obj.detail.intersection.uv.y;
128 |
129 | var mapSizeX=olMap.getSize()[0];
130 | var mapSizeY=olMap.getSize()[1];
131 |
132 | var objMapX=objX*mapSizeX;
133 | var objMapY=(1-objY)*mapSizeY; // in Y needs to be reversed because map origin is top left
134 | comp.simulateEvent(data.OlEvent,objMapX,objMapY,false,olMap);
135 | });
136 |
137 | // Postcompose event. This were we convert canvas to image and the assign this image to the aframe object
138 |
139 | olMap.on('postcompose', function(event) {
140 | var canvas = event.context.canvas;
141 | var img = canvas.toDataURL("image/png")
142 | //el.setAttribute("src", img);
143 | el.setAttribute("material","src", img);
144 | });
145 | olMap.renderSync();
146 | },
147 |
148 | //Simulated event function based on https://github.com/openlayers/openlayers/blob/83f87a1f1ee4d9a2e1e3954c908188c8a73cfb75/test/spec/ol/interaction/draw.test.js#L68
149 | simulateEvent:function (type,x,y,shiftKey,olMap){
150 | var event = new ol.pointer.PointerEvent(type, {
151 | clientX: x,
152 | clientY: y,
153 | shiftKey: shiftKey
154 | });
155 | olMap.handleMapBrowserEvent(new ol.MapBrowserPointerEvent(type, olMap, event)); //new ol.MapBrowserPointerEvent is available in openlayers debug
156 | },
157 | nearestPow2: function( aSize ){
158 | return Math.pow( 2, Math.round( Math.log( aSize ) / Math.log( 2 ) ) );
159 | },
160 |
161 | calculateNewSize: function (newMapSize, ratio ){
162 | return newMapSize/ratio;
163 | }
164 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aframe-openlayers-component",
3 | "version": "1.0.2",
4 | "description": "Use OpenLayers maps inside A-Frame",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "browserify examples/main.js -o examples/build.js",
8 | "dev": "node examples/dev.js examples/main.js:build.js --dir examples --port 8000 --live -t babelify",
9 | "dist": "webpack index.js dist/aframe-openlayers-component.js && webpack -p index.js dist/aframe-openlayers-component.min.js",
10 | "start": "npm run dev"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/lcalisto/aframe-openlayers-component.git"
15 | },
16 | "keywords": [
17 | "aframe",
18 | "aframe-component",
19 | "aframe-vr",
20 | "vr",
21 | "mozvr",
22 | "webvr",
23 | "openlayers",
24 | "gis"
25 | ],
26 | "author": "Luis Calisto",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/lcalisto/aframe-openlayers-component/issues"
30 | },
31 | "homepage": "https://github.com/lcalisto/aframe-openlayers-component#readme",
32 | "devDependencies": {
33 | "browserify": "^16.2.2",
34 | "serve-index": "^1.9.1",
35 | "webpack": "^4.10.2",
36 | "webpack-cli": "^2.1.4"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------