├── .gitignore
├── .gitmodules
├── Cesium.externs.js
├── LICENSE
├── Leaflet.externs.js
├── Makefile
├── README.md
├── api-debug.json
├── api.json
├── app-debug.json
├── app.json
├── deploy
├── api.html
├── header.js
├── main.css
├── polygon.html
├── stars.jpg
└── world512.jpg
├── src-app
├── main.html
└── main.js
└── src
├── api-l.html
├── api-l.js
├── api.html
├── api.js
├── app.js
├── camera.js
├── cameraanimator.js
├── canvas2image.js
├── custommap.js
├── editablepolygon.js
├── map.js
├── maps.js
├── markers
├── abstractmarker.js
├── basicmarker.js
├── markermanager.js
├── polydragger.js
├── popup.js
└── prettymarker.js
├── miniglobe.js
├── polygon.html
├── polygon.js
├── polyicon.js
└── utils.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /deploy/api.js
2 | /deploy/index.js
3 | /deploy/api_nocesium.js
4 | /deploy/index_nocesium.js
5 | /plovr-*.*
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "cesium"]
2 | path = cesium
3 | url = https://github.com/klokantech/cesium.git
4 | [submodule "src-app/library"]
5 | path = src-app/library
6 | url = https://github.com/klokantech/javascript.git
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Leaflet.externs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @externs
3 | * Very simple externs to prevent overwriting of L namespace
4 | */
5 | var L = {};
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PLOVR_VERSION=2.0.0
2 | PLOVR=plovr-$(PLOVR_VERSION).jar
3 |
4 | WEBGLEARTH_VERSION = $(firstword $(subst -, ,$(subst v,,$(shell git describe --tags))))
5 |
6 | .PHONY: plovr cesium lint webserver
7 |
8 | all: build
9 | plovr: $(PLOVR)
10 | $(PLOVR):
11 | wget -q --no-check-certificate https://registry.npmjs.org/plovr/-/plovr-$(PLOVR_VERSION).tgz
12 | tar -xOzf plovr-$(PLOVR_VERSION).tgz package/bin/plovr.jar > $(PLOVR)
13 | rm plovr-$(PLOVR_VERSION).tgz
14 | serve:
15 | java -jar $(PLOVR) serve -p 9810 *.json
16 | build:
17 | java -jar $(PLOVR) build api.json > deploy/api_nocesium.js
18 | sed -e 's#{WEBGLEARTH_VERSION}#$(WEBGLEARTH_VERSION)#' deploy/header.js > deploy/api.js
19 | cat cesium/Build/Cesium/Cesium.js >> deploy/api.js
20 | cat deploy/api_nocesium.js >> deploy/api.js
21 | java -jar $(PLOVR) build app.json > deploy/index_nocesium.js
22 | sed -e 's#{WEBGLEARTH_VERSION}#$(WEBGLEARTH_VERSION)#' deploy/header.js > deploy/index.js
23 | cat cesium/Build/Cesium/Cesium.js >> deploy/index.js
24 | cat deploy/index_nocesium.js >> deploy/index.js
25 | lint:
26 | fixjsstyle --strict -r ./src
27 | fixjsstyle --strict -r ./src-app
28 | gjslint --strict -r ./src
29 | gjslint --strict -r ./src-app
30 | webserver:
31 | java -jar $(PLOVR) soyweb -p 9820 --dir .
32 | cesium:
33 | cd cesium && "./Tools/apache-ant-1.8.2/bin/ant" minify
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### GRANDFATHERED
2 |
3 | This project is no longer maintained. Contact us if you want to become a maintainer of this project.
4 |
5 | We recommend using [Cesium](https://github.com/AnalyticalGraphicsInc/cesium) instead ([code sample](https://cloud.maptiler.com/maps/hybrid/cesium)).
6 |
7 | ---
8 |
9 | # WebGL Earth 2.0
10 |
11 | Open-source virtual planet web application running in any web browser with support for WebGL HTML5 standard. Mobile devices such as iPhone, iPad or Android based mobile phones are going to be supported too. It is a free software and a community driven project.
12 |
13 | There is an extremely easy to use JavaScript API - fully mimicking LeafletJS.
14 |
15 | See: http://examples.webglearth.org/ for demos.
16 |
17 | ## Usage
18 |
19 | ### JavaScript API
20 |
21 | WebGL Earth in version 2.0 is adapting the popular LeafletJS API. So if you are familiar with Leaflet you can easily start to use WebGL Earth, things like markers, popups, centering and flying to a place on given latitude and longitude are possible. The code from Leaflet can be also mixed with WE - you can pass L.LatLng and L.LatLngBounds, etc.
22 |
23 | A simple Hello World:
24 |
25 | ```html
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 | ```
42 | More examples are at: http://examples.webglearth.org/.
43 |
44 | Original version of WebGL Earth had also it's own easy API which is preserved for back compatibility.
45 | It is documented at http://www.webglearth.org/api.
46 |
47 | The supported Leaflet API methods are not yet documented -- the best source of the information are currently the API examples (`src/api-l.html`, `src/api.html`, `src/polygon.html`) and the API symbol exports itselves (`src/api.js` and `src/api-l.js`).
48 |
49 | ### Online hosted API
50 |
51 | The project API is available from Google CDN and can be linked and called directly from your web application as `http://www.webglearth.com/v2/api.js`. Examples mentioned above are demonstrating this form of use.
52 |
53 | Embedding of a globe in your own web is then extremely easy.
54 |
55 | ### From custom server or even offline
56 |
57 | Because the project is 100% open-source, the complete code can be hosted also on your own website or distributed with applications. Ready to use API releases are at: https://github.com/webglearth/webglearth2/releases
58 |
59 | With custom rendered map tiles (made with http://www.maptiler.com/) the project can be used on intranets, in restricted environments or even offline. The API should be accessed via HTTP protocol (possibly via localhost).
60 |
61 |
62 | ## Development and building
63 |
64 | * Clone the code from GitHub, including the submodules (cesium).
65 | * Use `make cesium` to build the required component.
66 |
67 | ### Running the app
68 | * Run `make serve` to start the plovr server (port 9810)
69 | * Open any of `src/*.html`
70 |
71 | ### Build the app
72 | * Run `make build` to produce `deploy/*_nocesium.js`
73 | * Combine the sources together to produce a single JavaScript file:
74 |
75 | ```
76 | /**
77 | * WebGL Earth 2.0
78 | * ===============
79 | * Copyright (C) 2014 - Klokan Technologies GmbH
80 | * http://www.webglearth.org/
81 | * Powered by Cesium (http://www.webglearth.org/cesium). Apache 2.0 license.
82 | */
83 |
84 | CESIUM_BASE_URL = '.';
85 | {content of cesium/Build/Cesium/Cesium.js}
86 | {content of deploy/api_nocesium.js}
87 | ```
88 |
89 | ## Supported by
90 |
91 | - Klokan Technologies GmbH
92 | - Analytical Graphics, Inc.
93 | - Tween Globe Ltd
94 | - Miizee.com
95 |
--------------------------------------------------------------------------------
/api-debug.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "api-debug",
3 | "inherits": "api.json",
4 |
5 | "define": {
6 | "goog.DEBUG": true
7 | },
8 |
9 | "mode": "ADVANCED",
10 |
11 | "pretty-print": true,
12 | "debug": true
13 | }
14 |
--------------------------------------------------------------------------------
/api.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "api",
3 |
4 | "inputs": ["src/api.js", "src/api-l.js"],
5 | "paths": ['src'],
6 |
7 | "externs": [
8 | "Cesium.externs.js",
9 | "Leaflet.externs.js"
10 | ],
11 |
12 | "define": {
13 | // "goog.dom.ASSUME_STANDARDS_MODE": true,
14 | "goog.DEBUG": false
15 | },
16 |
17 | "mode": "ADVANCED",
18 | "level": "VERBOSE",
19 |
20 | "output-wrapper": "(function() {%output%})();",
21 |
22 | // "pretty-print": true,
23 | // "debug": true,
24 |
25 | "checks": {
26 | // Unfortunately, the Closure Library violates these in many places.
27 | // "accessControls": "ERROR",
28 | // "visibility": "ERROR"
29 |
30 | "checkTypes": "ERROR",
31 | "checkRegExp": "ERROR",
32 | "checkVars": "ERROR",
33 | "deprecated": "ERROR",
34 | "fileoverviewTags": "ERROR",
35 | "invalidCasts": "ERROR",
36 | "missingProperties": "ERROR",
37 | "nonStandardJsDocs": "OFF",
38 | "undefinedVars": "ERROR"
39 | },
40 |
41 | "jsdoc-html-output-path": "."
42 | }
43 |
--------------------------------------------------------------------------------
/app-debug.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "app-debug",
3 | "inherits": "app.json",
4 |
5 | "define": {
6 | "goog.DEBUG": true,
7 | "weapi.GENERATE_EXPORTS": false
8 | },
9 |
10 | "mode": "SIMPLE",
11 |
12 | "pretty-print": true,
13 | "debug": true
14 | }
15 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "app",
3 |
4 | "inputs": ["src-app/main.js"],
5 | "paths": ["src", "src-app", "src-app/library/src"],
6 |
7 | "externs": [
8 | "Cesium.externs.js"
9 | ],
10 |
11 | "define": {
12 | // "goog.dom.ASSUME_STANDARDS_MODE": true,
13 | "goog.DEBUG": false,
14 | "weapi.GENERATE_EXPORTS": false
15 | },
16 |
17 | "mode": "ADVANCED",
18 | "level": "VERBOSE",
19 |
20 | // "pretty-print": true,
21 | // "debug": true,
22 |
23 | "checks": {
24 | // Unfortunately, the Closure Library violates these in many places.
25 | // "accessControls": "ERROR",
26 | // "visibility": "ERROR"
27 |
28 | "checkTypes": "ERROR",
29 | "checkRegExp": "ERROR",
30 | "checkVars": "ERROR",
31 | "deprecated": "ERROR",
32 | "fileoverviewTags": "ERROR",
33 | "invalidCasts": "ERROR",
34 | "missingProperties": "ERROR",
35 | "nonStandardJsDocs": "OFF",
36 | "undefinedVars": "ERROR"
37 | },
38 |
39 | "jsdoc-html-output-path": "."
40 | }
41 |
--------------------------------------------------------------------------------
/deploy/api.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WebGL Earth API - Demo
8 |
9 |
10 |
11 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Pick color for position ,
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/deploy/header.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WebGL Earth {WEBGLEARTH_VERSION}
3 | * ===============
4 | * Copyright (C) 2014 - Klokan Technologies GmbH
5 | * http://www.webglearth.org/
6 | * Powered by Cesium (http://www.webglearth.org/cesium). Apache 2.0 license.
7 | */
8 |
9 | //------------------------------------------------------------------------------
10 | CESIUM_BASE_URL = ('https:' == document.location.protocol ? 'https:' : 'http:') + '//www.webglearth.com/v2/';
11 |
--------------------------------------------------------------------------------
/deploy/main.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:300,600,700&subset=latin,latin-ext);
2 |
3 | @font-face {
4 | font-family: 'webglearth';
5 | src:url('fonts/webglearth.eot?8bl3d6');
6 | src:url('fonts/webglearth.eot?#iefix8bl3d6') format('embedded-opentype'),
7 | url('fonts/webglearth.svg?8bl3d6#webglearth') format('svg'),
8 | url('fonts/webglearth.woff?8bl3d6') format('woff'),
9 | url('fonts/webglearth.ttf?8bl3d6') format('truetype');
10 | font-weight: normal;
11 | font-style: normal;
12 | }
13 |
14 | html, body {width:100%;height:100%;}
15 | html, body, div {padding:0; margin:0;}
16 | body, input, select {font-family:'Open Sans',Arial,sans-serif;}
17 | img {border:none;}
18 |
19 | #logo, #klokan, #share, #sidelinks li a:before, #sidelinks li a:after {font-family:'webglearth';text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;overflow:hidden;text-shadow:#333 0 0 5px;}
20 |
21 | #logo {position:absolute;top:40px;left:0;width:200px;height:55px;line-height:55px;z-index:1;background:rgba(255,255,255,0.15);text-align:center;}
22 | #logo:before {content:"\e607";font-size:175px;color:#fff;}
23 | #geocoder {position:absolute;top:40px;height:24px;right:0;width:204px;z-index:1;padding:2px 4px;border:none;}
24 | .ac-renderer {position:absolute;right:0;width:204px;z-index:1;font-size:12px;background:#fff;border:0 solid #000;border-width:1px 0;overflow:hidden;white-space:nowrap;}
25 | .ac-row {border-bottom:1px solid #666;width:100%;overflow:hidden;text-overflow:ellipsis;padding:1px 0 1px 2px;cursor:pointer;}
26 | .ac-row .suffix {
27 | font-size:.9em;
28 | color:#aaa;
29 | padding:.1em 0 .1em 5px;
30 | }
31 | .ac-row .type {
32 | font-size:.8em;
33 | color:#888;
34 | display:block;
35 | width:100%;
36 | }
37 | .ac-row:hover {background:#ccc;}
38 | #maptype {position:absolute;top:67px;height:24px;right:0;width:204px;z-index:1;border:none;}
39 |
40 | .link {color:#eee;background:rgba(255,255,255,0.15);}
41 | .link:hover {color:#fff;background:rgba(255,255,255,0.175);}
42 |
43 | #klokan {position:absolute;bottom:62px;right:0;width:231px;z-index:1;height:58px;}
44 | #klokan:before {content:"\e600";font-size:225px;line-height:58px;}
45 | #webglearthdiv {position:absolute !important;top:0;right:0;bottom:0;left:0;background:#000 url(stars.jpg);background-size:cover;touch-action:none;}
46 |
47 | #share {position:absolute;bottom:75px;left:0;padding:0;list-style:none;margin:0;}
48 | #share li {float:left;line-height:32px;margin-right:2px;}
49 | #share li a {font-size:32px;overflow:hidden;display:block;width:33px;height:32px;text-decoration:none;}
50 | #share .gplus:before {content:"\e606";}
51 | #share .facebook:before {content:"\e605";}
52 | #share .twitter:before {content:"\e604";}
53 |
54 | #sidelinks {position:absolute;top:125px;right:0;padding:0;list-style:none;margin:0;}
55 | #sidelinks li {font-size:16px;line-height:18px;margin-bottom:2px;}
56 | #sidelinks li a {overflow:hidden;display:block;width:120px;text-decoration:none;text-align:center;padding:10px 0;}
57 | #sidelinks .maptiler:after {display:block;content:"\e601";font-size:90px;line-height:90px;}
58 | #sidelinks .jsapi:before {display:block;content:"\e602";font-size:45px;line-height:55px;}
59 | #sidelinks .github:before {display:block;content:"\e603";font-size:45px;line-height:55px;}
60 | .jsapi, .github {background:none;}
61 |
62 | .plovr-error-report {position:absolute;z-index:10;}
63 |
64 | .cesium-credit-textContainer a {color:#fff !important;}
65 |
--------------------------------------------------------------------------------
/deploy/polygon.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WebGL Earth API - Polygon Example
8 |
9 |
10 |
11 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | Change fill color:
158 |
159 | #7bfffb
160 | #f8ff7d
161 | #5aff5a
162 | #fc7361
163 | #ee84fc
164 | #bab3b8
165 |
166 | Change border color:
167 |
168 | #7ad3ff
169 | #ffdb74
170 | #fc2009
171 | #fc7361
172 | #f225fc
173 | #7b7679
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
--------------------------------------------------------------------------------
/deploy/stars.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webglearth/webglearth2/ec69cf65d9fb32ca698ed01bd017347a8c39bff0/deploy/stars.jpg
--------------------------------------------------------------------------------
/deploy/world512.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webglearth/webglearth2/ec69cf65d9fb32ca698ed01bd017347a8c39bff0/deploy/world512.jpg
--------------------------------------------------------------------------------
/src-app/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WebGL Earth
8 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
24 | KlokanTech Satellite Hybrid
25 | KlokanTech Streets
26 | OpenStreetMap
27 |
28 |
29 |
34 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src-app/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapp.App');
9 |
10 | goog.require('goog.dom');
11 | goog.require('goog.dom.ViewportSizeMonitor');
12 | goog.require('goog.events');
13 | goog.require('goog.events.EventType');
14 | goog.require('goog.net.Jsonp');
15 | goog.require('goog.userAgent');
16 |
17 | goog.require('kt.OsmNamesAutocomplete');
18 | goog.require('weapi.exports.App');
19 |
20 |
21 |
22 | /**
23 | * @constructor
24 | */
25 | weapp.App = function() {
26 | if (!weapi.App.detectWebGLSupport()) {
27 | window.location = '//www.webglearth.com/webgl-error.html';
28 | }
29 |
30 | var thkey = 'kSwrAdFeuIo6rD2Bm9dc';
31 |
32 | /**
33 | * @type {!weapi.exports.App}
34 | * @private
35 | */
36 | this.app_ = new weapi.exports.App('webglearthdiv', {
37 | 'atmosphere': true,
38 | 'sky': false,
39 | 'terrain': 'https://api.maptiler.com/tiles/terrain-quantized-mesh/layer.json?key=' + thkey + '&',
40 | 'terrainCredit': '', // suppress the html credit
41 | 'position': [0, 0],
42 | 'altitude': weapp.App.DEFAULT_ALT,
43 | 'panning': true,
44 | 'tilting': true,
45 | 'zooming': true,
46 | 'proxyHost': '//srtm.webglearth.com/cgi-bin/corsproxy.fcgi?url='
47 | });
48 |
49 | if (window.location.hash.length < 4) {
50 | new goog.net.Jsonp('https://freegeoip.klokantech.com/json/').send(
51 | undefined, goog.bind(function(data) {
52 | if (data) {
53 | var lat = data['latitude'], lng = data['longitude'];
54 | if (!isNaN(lat) && !isNaN(lng)) {
55 | this.app_.setPosition(lat, lng);
56 | }
57 | }
58 | }, this));
59 | }
60 |
61 | /**
62 | * @type {!goog.dom.ViewportSizeMonitor}
63 | * @private
64 | */
65 | this.vsm_ = new goog.dom.ViewportSizeMonitor();
66 | goog.events.listen(this.vsm_, goog.events.EventType.RESIZE, function(e) {
67 | this.resize_(this.vsm_.getSize());
68 | }, false, this);
69 | this.resize_(this.vsm_.getSize());
70 |
71 | var geocoderElement = /** @type {!Element} */
72 | (goog.dom.getElement('geocoder'));
73 |
74 | var autocomplete = new kt.OsmNamesAutocomplete(geocoderElement,
75 | 'https://geocoder.tilehosting.com/', 'oeGRMGLUngqm8fMN45zj');
76 |
77 | autocomplete.registerCallback(function(item) {
78 | console.log(item['boundingbox']);
79 | this.app_.flyToFitBounds(
80 | item['boundingbox'][1],
81 | item['boundingbox'][3],
82 | item['boundingbox'][0],
83 | item['boundingbox'][2]
84 | );
85 | }.bind(this));
86 |
87 | var initedMaps = {}; //cache
88 | var maptypeElement = /** @type {!HTMLSelectElement} */
89 | (goog.dom.getElement('maptype'));
90 | var updateLayer = goog.bind(function() {
91 | var key = maptypeElement.options[maptypeElement.selectedIndex].value;
92 | switch (key) {
93 | default:
94 | if (!goog.isDefAndNotNull(initedMaps[key])) {
95 | initedMaps[key] = this.app_.initMap(weapi.maps.MapType.CUSTOM, {
96 | 'url': 'https://api.maptiler.com/maps/' + key +
97 | '/{z}/{x}/{y}.png?key=' + thkey,
98 | 'tileSize': 512,
99 | 'maximumLevel': 18,
100 | 'copyright': '© MapTiler © OpenStreetMap contributors',
101 | 'copyrightLink': 'https://www.maptiler.com/copyright/'
102 | });
103 | }
104 | this.app_.setBaseMap(initedMaps[key]);
105 | break;
106 | case 'hybrid':
107 | if (!goog.isDefAndNotNull(initedMaps[key])) {
108 | initedMaps[key] = this.app_.initMap(weapi.maps.MapType.CUSTOM, {
109 | 'url': 'https://api.maptiler.com/maps/hybrid' +
110 | '/{z}/{x}/{y}.jpg?key=' + thkey,
111 | 'tileSize': 512,
112 | 'maximumLevel': 16,
113 | 'copyright': '© MapTiler © OpenStreetMap contributors',
114 | 'copyrightLink': 'https://www.maptiler.com/copyright/'
115 | });
116 | }
117 | this.app_.setBaseMap(initedMaps[key]);
118 | break;
119 | case 'osm':
120 | if (!goog.isDefAndNotNull(initedMaps[key])) {
121 | initedMaps[key] = this.app_.initMap(weapi.maps.MapType.OSM);
122 | }
123 | this.app_.setBaseMap(initedMaps[key]);
124 | break;
125 | }
126 | }, this);
127 | goog.events.listen(maptypeElement, goog.events.EventType.CHANGE, updateLayer);
128 | updateLayer();
129 |
130 | /* HASH UPDATING & PARSING */
131 |
132 | /**
133 | * @type {string}
134 | * @private
135 | */
136 | this.lastCreatedHash_ = '';
137 |
138 | var updateHash = goog.bind(function() {
139 | var pos = this.app_.getPosition();
140 | var newhash = '#ll=' + pos[0].toFixed(5) + ',' + pos[1].toFixed(5) +
141 | ';alt=' + this.app_.getAltitude().toFixed(0);
142 | var head = this.app_.getHeading(), tilt = this.app_.getTilt();
143 | if (Math.abs(head) > 0.001) newhash += ';h=' + head.toFixed(3);
144 | if (Math.abs(tilt) > 0.001) newhash += ';t=' + tilt.toFixed(3);
145 | if (window.location.hash.toString() != newhash) {
146 | this.lastCreatedHash_ = newhash;
147 | window.location.hash = newhash;
148 | }
149 | }, this);
150 |
151 | var parseHash = goog.bind(function() {
152 | if (window.location.hash == this.lastCreatedHash_) return;
153 | var params = window.location.hash.substr(1).split(';');
154 | var getValue = function(name) {
155 | name += '=';
156 | var pair = goog.array.find(params, function(el, i, a) {
157 | return el.indexOf(name) === 0;});
158 |
159 | if (goog.isDefAndNotNull(pair)) {
160 | var value = pair.substr(name.length);
161 | if (value.length > 0)
162 | return value;
163 | }
164 | return undefined;
165 | };
166 |
167 | var ll = getValue('ll'), altitude = getValue('alt');
168 | var heading = getValue('h'), tilt = getValue('t');
169 | if (goog.isDefAndNotNull(ll)) {
170 | var llsplit = ll.split(',');
171 | if (llsplit.length > 1 && !isNaN(llsplit[0]) && !isNaN(llsplit[1])) {
172 | if (!altitude || isNaN(altitude)) altitude = weapp.App.DEFAULT_ALT;
173 | if (!tilt || isNaN(tilt)) tilt = 0;
174 | if (!heading || isNaN(heading)) heading = 0;
175 | this.app_.setPosition(parseFloat(llsplit[0]), parseFloat(llsplit[1]),
176 | undefined, parseFloat(altitude),
177 | parseFloat(heading), parseFloat(tilt));
178 | }
179 | }
180 | }, this);
181 |
182 | /**
183 | * @type {!goog.Timer}
184 | */
185 | this.hashUpdateTimer = new goog.Timer(2000);
186 | goog.events.listen(this.hashUpdateTimer, goog.Timer.TICK, updateHash);
187 | this.hashUpdateTimer.start();
188 |
189 | goog.events.listen(window, goog.events.EventType.HASHCHANGE, parseHash);
190 |
191 | parseHash();
192 |
193 | if (goog.userAgent.MOBILE || goog.userAgent.ANDROID ||
194 | goog.userAgent.IPHONE || goog.userAgent.IPAD) {
195 | var hideAddressBar = function() {
196 | if (document.documentElement.scrollHeight <
197 | window.outerHeight / window.devicePixelRatio)
198 | document.documentElement.style.height =
199 | (window.outerHeight / window.devicePixelRatio) + 'px';
200 | setTimeout(window.scrollTo(1, 1), 0);
201 | };
202 | goog.events.listen(window, 'orientationchange', hideAddressBar);
203 | hideAddressBar();
204 | } else {
205 | geocoderElement.focus();
206 | }
207 | };
208 |
209 |
210 | /**
211 | * @define {number} default altitude in meters.
212 | */
213 | weapp.App.DEFAULT_ALT = 17000000;
214 |
215 |
216 | /**
217 | * @param {?goog.math.Size} size
218 | * @private
219 | */
220 | weapp.App.prototype.resize_ = function(size) {
221 | if (!size) return;
222 | this.app_.handleResize();
223 | };
224 |
225 | goog.exportSymbol('WebGLEarth', weapp.App);
226 |
--------------------------------------------------------------------------------
/src/api-l.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WebGL Earth API - Basic Leaflet compatibility
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
132 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 | Second layer opacity: 0 1
157 |
158 | Grand Canyon overlay opacity: 0 1
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/src/api-l.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Additional Leaflet-style API
3 | *
4 | * @author petr.sloup@klokantech.com (Petr Sloup)
5 | *
6 | * Copyright 2014 Klokan Technologies Gmbh (www.klokantech.com)
7 | */
8 |
9 | goog.provide('weapi.exportsL');
10 |
11 | goog.require('goog.net.Jsonp');
12 |
13 | goog.require('weapi');
14 | goog.require('weapi.exports.App');
15 | goog.require('weapi.maps');
16 |
17 |
18 | /**
19 | * @define {boolean} Generate these exports?
20 | */
21 | weapi.GENERATE_EXPORTS_L = true;
22 |
23 | var exportSymbolL = function(symbol, obj) {
24 | if (weapi.GENERATE_EXPORTS_L) goog.exportSymbol(symbol, obj);
25 | };
26 |
27 |
28 | exportSymbolL('WE.map', function(id, opt_opts) {
29 | var opts = opt_opts || {};
30 | var z = opts['zoom'];
31 | opts['zoom'] = undefined;
32 | opts['empty'] = true;
33 | var app = new weapi.exports.App(id, opts);
34 | if (z) app.setZoom(z);
35 | return app;
36 | });
37 |
38 |
39 | exportSymbolL('WebGLEarth.prototype.setView', function(center, opt_zoom) {
40 | if (!goog.isArray(center)) center = [center['lat'], center['lng']];
41 | this.setPosition(center[0], center[1], opt_zoom, undefined, 0, 0);
42 | });
43 |
44 |
45 | exportSymbolL('WebGLEarth.prototype.zoomIn', function(opt_delta) {
46 | this.setZoom(this.getZoom() + (goog.isNumber(opt_delta) ? opt_delta : 1));
47 | });
48 |
49 |
50 | exportSymbolL('WebGLEarth.prototype.zoomOut', function(opt_delta) {
51 | this.setZoom(this.getZoom() - (goog.isNumber(opt_delta) ? opt_delta : 1));
52 | });
53 |
54 |
55 | exportSymbolL('WebGLEarth.prototype.panInsideBounds', function(bnds, opt_opts) {
56 | if (!goog.isArray(bnds))
57 | bnds = [bnds.getSouth(), bnds.getNorth(), bnds.getWest(), bnds.getEast()];
58 | if (goog.isArray(bnds[0]))
59 | bnds = [bnds[0][0], bnds[1][0], bnds[0][1], bnds[1][1]];
60 | opt_opts = opt_opts || {};
61 | this.flyToFitBounds(bnds[0], bnds[1], bnds[2], bnds[3],
62 | opt_opts['heading'], opt_opts['tilt'],
63 | opt_opts['duration']);
64 | });
65 |
66 |
67 | exportSymbolL('WebGLEarth.prototype.fitBounds', function(bnds, opt_opts) {
68 | if (!goog.isArray(bnds))
69 | bnds = [bnds.getSouth(), bnds.getNorth(), bnds.getWest(), bnds.getEast()];
70 | if (goog.isArray(bnds[0]))
71 | bnds = [bnds[0][0], bnds[1][0], bnds[0][1], bnds[1][1]];
72 | opt_opts = opt_opts || {};
73 |
74 | var minlat = goog.math.toRadians(bnds[0]);
75 | var maxlat = goog.math.toRadians(bnds[1]);
76 | var minlon = goog.math.toRadians(bnds[2]);
77 | var maxlon = goog.math.toRadians(bnds[3]);
78 | var opt_heading = opt_opts['heading'], opt_tilt = opt_opts['tilt'];
79 |
80 | var altitude = this.camera.calcDistanceToViewBounds(minlat, maxlat,
81 | minlon, maxlon);
82 |
83 | minlon = goog.math.modulo(minlon, 2 * Math.PI);
84 | maxlon = goog.math.modulo(maxlon, 2 * Math.PI);
85 |
86 | var lonDiff = minlon - maxlon;
87 | if (lonDiff < -Math.PI) {
88 | minlon += 2 * Math.PI;
89 | } else if (lonDiff > Math.PI) {
90 | maxlon += 2 * Math.PI;
91 | }
92 |
93 | var center = [(minlat + maxlat) / 2, (minlon + maxlon) / 2];
94 |
95 | this.setPosition(
96 | goog.math.toDegrees(center[0]), goog.math.toDegrees(center[1]),
97 | undefined, altitude,
98 | opt_heading, opt_tilt, goog.isDef(opt_heading) || goog.isDef(opt_tilt));
99 | });
100 |
101 |
102 | exportSymbolL('WebGLEarth.prototype.panTo', function(center, opt_opts) {
103 | if (!goog.isArray(center)) center = [center['lat'], center['lng']];
104 | opt_opts = opt_opts || {};
105 | this.flyTo(center[0], center[1], undefined, undefined, 0, 0,
106 | undefined, opt_opts['duration']);
107 | });
108 |
109 |
110 | exportSymbolL('WE.tileLayer', function(url, opt_opts) {
111 | var opts = opt_opts || {};
112 | goog.object.forEach(opts, function(val, k, arr) {
113 | url = url.replace('{' + k + '}', val);
114 | });
115 | url = url.replace('{s}', '{sub}');
116 | var subdoms = opts['subdomains'] || 'abc';
117 | if (goog.isString(subdoms)) subdoms = subdoms.split('');
118 | var bnds = opts['bounds'];
119 | if (bnds && goog.isArray(bnds[0]))
120 | bnds = [bnds[0][0], bnds[1][0], bnds[0][1], bnds[1][1]];
121 | return weapi.maps.initMap(null, weapi.maps.MapType.CUSTOM, {
122 | 'url': url,
123 | 'minimumLevel': opts['minZoom'] || 0,
124 | 'maximumLevel': opts['maxZoom'] || 18,
125 | 'tileSize': opts['tileSize'] || 256,
126 | 'flipY': opts['tms'] || false,
127 | 'subdomains': subdoms,
128 | 'copyright': (opts['attribution'] || '').replace(/<(?:.|\n)*?>/gm, ''),
129 | 'opacity': opts['opacity'],
130 | 'bounds': bnds
131 | });
132 | });
133 |
134 |
135 | exportSymbolL('WE.tileLayerJSON', function(data, opt_app) {
136 | var load = function(data, opt_app, opt_map) {
137 | var url = data['tiles'][0];
138 | var attribution = data['attribution'];
139 | var minzoom = data['minzoom'];
140 | var maxzoom = data['maxzoom'];
141 | var bnds = data['bounds'];
142 | var center = data['center'];
143 |
144 | var opts = {
145 | 'url': url,
146 | 'minimumLevel': minzoom || 0,
147 | 'maximumLevel': maxzoom || 18,
148 | 'copyright': (attribution || '').replace(/<(?:.|\n)*?>/gm, ''),
149 | 'bounds': bnds ? [bnds[1], bnds[3], bnds[0], bnds[2]] : undefined
150 | };
151 | var map;
152 |
153 | if (opt_map) {
154 | var prov = /** @type {!weapi.CustomMap} */(opt_map.layer.imageryProvider);
155 | prov.setOptions(opts);
156 | map = opt_map;
157 | if (data['opacity'] && map.getOpacity() == 1)
158 | map.setOpacity(parseFloat(data['opacity']));
159 | if (bnds) map.setBoundingBox(bnds[1], bnds[3], bnds[0], bnds[2]);
160 | } else {
161 | map = weapi.maps.initMap(null, weapi.maps.MapType.CUSTOM, opts);
162 | }
163 |
164 | if (opt_app) {
165 | map['addTo'](opt_app);
166 | if (center && center.length && center.length > 1) {
167 | opt_app.setPosition(center[1], center[0]);
168 | if (center.length > 2) {
169 | opt_app.setZoom(center[2]);
170 | }
171 | }
172 | }
173 | return map;
174 | };
175 | var map;
176 | if (goog.isString(data)) {
177 | map = weapi.maps.initMap(null, weapi.maps.MapType.CUSTOM, {});
178 | var jsonp = new goog.net.Jsonp(data);
179 | jsonp.send(undefined, function(data) {
180 | load(data, opt_app, map);
181 | });
182 | } else {
183 | map = load(data, opt_app, undefined);
184 | }
185 |
186 | return map;
187 | });
188 |
189 |
190 | exportSymbolL('WebGLEarth.Map.prototype.addTo', function(app) {
191 | this.layer.imageryProvider.proxy = app.mapProxyObject;
192 | var layers = app.scene.imageryLayers;
193 | layers.add(this.layer);
194 | app.listenCORSErrors(this.layer.imageryProvider['errorEvent']);
195 | this.app = app;
196 | app.sceneChanged = true;
197 |
198 | return this;
199 | });
200 |
201 |
202 | exportSymbolL('WebGLEarth.Map.prototype.removeFrom', function(app) {
203 | app = this.app;
204 | var layers = app.scene.imageryLayers;
205 | layers.remove(this.layer);
206 | app.sceneChanged = true;
207 | this.app = null;
208 |
209 | return this;
210 | });
211 |
212 |
213 | exportSymbolL('WE.marker', function(pos, opt_iconUrl, opt_width, opt_height) {
214 | if (!goog.isArray(pos)) pos = [pos['lat'], pos['lng']];
215 | var mark = new weapi.markers.PrettyMarker(goog.math.toRadians(pos[0]),
216 | goog.math.toRadians(pos[1]),
217 | opt_iconUrl, opt_width, opt_height);
218 | return mark;
219 | });
220 |
221 |
222 | exportSymbolL('WebGLEarth.Marker.prototype.addTo', function(app) {
223 | app.markerManager.addMarker(goog.getUid(this).toString(), this);
224 | app.sceneChanged = true;
225 | return this;
226 | });
227 |
228 |
229 | exportSymbolL('WebGLEarth.Marker.prototype.removeFrom', function(app) {
230 | app.markerManager.removeMarker(goog.getUid(this).toString());
231 | app.sceneChanged = true;
232 | return this;
233 | });
234 |
235 |
236 | exportSymbolL('WebGLEarth.Marker.prototype.bindPopup',
237 | function(content, maxWOrOpts, closeBtn) {
238 | if (!goog.isDefAndNotNull(maxWOrOpts) || goog.isNumber(maxWOrOpts)) {
239 | this.attachPopup(new weapi.markers.Popup(content,
240 | maxWOrOpts, closeBtn));
241 | } else {
242 | var maxWidth = maxWOrOpts['maxWidth'];
243 | closeBtn = maxWOrOpts['closeButton'];
244 | this.attachPopup(new weapi.markers.Popup(content, maxWidth, closeBtn));
245 | }
246 | return this;
247 | });
248 |
249 |
250 | exportSymbolL('WebGLEarth.Marker.prototype.setLatLng', function(pos) {
251 | if (!goog.isArray(pos)) pos = [pos['lat'], pos['lng']];
252 | this.setPosition(pos[0], pos[1]);
253 | });
254 |
255 |
256 | exportSymbolL('WE.polygon', function(points, opts) {
257 | // our design is not prepared for polygons not assigned to any app -> hack
258 | return {
259 | 'addTo': /** @suppress {accessControls} */function(app) {
260 | //WARNING: addTo returns something different than WE.polygon !
261 | var poly = new weapi.exports.Polygon(app);
262 | var points_ = [];
263 | goog.array.forEachRight(points, function(el, i, arr) {
264 | if (!goog.isArray(el)) el = [el['lat'], el['lng']];
265 | points_.push([el[0], el[1]]);
266 | });
267 | poly.addPoints(points_);
268 | opts = opts || {};
269 | poly.showDraggers(opts['editable'] == true);
270 | poly.setStrokeColor(opts['color'] || '#03f',
271 | opts['opacity'] || 0.5);
272 | poly.setFillColor(opts['fillColor'] || '#03f',
273 | opts['fillOpacity'] || 0.2);
274 | poly.polygon_.primitiveLine.width = opts['weight'] || 5; //TODO: cleaner
275 | return poly;
276 | }
277 | };
278 | });
279 |
--------------------------------------------------------------------------------
/src/api.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WebGL Earth API - Hello World
8 |
9 |
10 |
11 |
12 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | Pick color for position ,
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.App');
9 |
10 | goog.require('goog.dom');
11 |
12 | goog.require('weapi.Camera');
13 | goog.require('weapi.maps');
14 | goog.require('weapi.markers.MarkerManager');
15 | goog.require('weapi.markers.PrettyMarker');
16 | goog.require('weapi.utils');
17 |
18 |
19 | /**
20 | * @define {number} API version.
21 | */
22 | weapi.VERSION = 2;
23 |
24 |
25 | /**
26 | * @define {string} .
27 | */
28 | weapi.UA = 'UA-20846306-1';
29 |
30 |
31 |
32 | /**
33 | *
34 | * @param {string} divid .
35 | * @param {Object=} opt_options Application options.
36 | * @constructor
37 | */
38 | weapi.App = function(divid, opt_options) {
39 | var options = opt_options || {};
40 | var container = goog.dom.getElement(divid);
41 |
42 | var webGLSupported = weapi.App.detectWebGLSupport();
43 |
44 | this.isFileProtocol = window.location.protocol == 'file:';
45 | this.resourceProtocol = this.isFileProtocol ? 'http:' : '';
46 |
47 | if (weapi.UA && weapi.UA.length > 0) {
48 | var trackerVar = '__WE_ga'; //global variable
49 | (function(i, s, o, g, r) {
50 | i['GoogleAnalyticsObject'] = r;
51 | i[r] = i[r] || function() {(i[r]['q'] = i[r]['q'] || []).push(arguments)};
52 | i[r]['l'] = 1 * new Date();
53 | var a = s.createElement(o), m = s.getElementsByTagName(o)[0];
54 | a.async = 1;
55 | a.src = g;
56 | m.parentNode.insertBefore(a, m);
57 | })(window, document, 'script',
58 | this.resourceProtocol + '//www.google-analytics.com/analytics.js',
59 | trackerVar);
60 | window[trackerVar]('create', weapi.UA, {'name': 'we0'});
61 | window[trackerVar]('we0.send', 'event', weapi.VERSION.toString(),
62 | window.location.host, window.location.href,
63 | webGLSupported ? 1 : 0);
64 | }
65 |
66 | if (!webGLSupported) {
67 | var ifr = goog.dom.createDom('iframe', {
68 | 'src': this.resourceProtocol + '//www.webglearth.com/webgl-error.html'
69 | });
70 | ifr.style.width = '100%';
71 | ifr.style.height = '100%';
72 | ifr.style.border = 'none';
73 | container.appendChild(ifr);
74 | return;
75 | }
76 |
77 | weapi.utils.installStyles('.cesium-credit-textContainer:before{content:' +
78 | '\'WebGL Earth \\2022\\20 Cesium \\2022\\20\';}');
79 |
80 | this.CORSErrorReported = false;
81 | weapi.maps.initStatics(this);
82 |
83 | container.style.position = 'relative';
84 | container.style.overflow = 'hidden';
85 | this.canvas = /** @type {!HTMLCanvasElement} */
86 | (goog.dom.createElement('canvas'));
87 | this.canvas.style.width = '100%';
88 | this.canvas.style.height = '100%';
89 | this.canvas.oncontextmenu = function() {return false;};
90 | container.appendChild(this.canvas);
91 |
92 | /** @type {?Function} */
93 | this.afterFrameOnce = null;
94 |
95 | this.scene = new Cesium.Scene({
96 | 'canvas': this.canvas,
97 | 'contextOptions': {'webgl': {'alpha': options['sky'] !== true}}
98 | });
99 |
100 | /** @type {?weapi.MiniGlobe} */
101 | this.miniglobe = null;
102 |
103 | /** @type {boolean} */
104 | this.forcedPause = false;
105 |
106 | /** @type {boolean} */
107 | this.sceneChanged = true;
108 |
109 | // Debug stats for the rendering: (uncomment here and stats[_]++ below)
110 | //var stats = [0, 0];
111 | //setInterval(function() {
112 | // window['console']['log']('rendered:', stats[0], 'ommited:', stats[1],
113 | // 'rendered %:', stats[0] / (stats[0] + stats[1]));
114 | //}, 2000);
115 |
116 | /** @type {!Cesium.Matrix4} */
117 | this.lastViewMatrix = new Cesium.Matrix4();
118 |
119 | /** @type {?string} */
120 | var proxyHost = options['proxyHost'] || null;
121 |
122 | /* type {{getURL: function(string) : string}} */
123 | this.mapProxyObject = {
124 | 'getURL': function(url) {
125 | return goog.isDefAndNotNull(proxyHost) ?
126 | proxyHost + encodeURIComponent(url) : url;
127 | }
128 | };
129 |
130 | if (options['atmosphere']) {
131 | this.scene.skyAtmosphere = new Cesium.SkyAtmosphere();
132 | }
133 | if (options['sky']) {
134 | var baseUrl = goog.isString(options['sky']) ?
135 | options['sky'] : window['CESIUM_BASE_URL'];
136 | var skyBoxBaseUrl = (goog.DEBUG ? '../deploy/' : baseUrl) + 'SkyBox/';
137 | this.scene.skyBox = new Cesium.SkyBox({
138 | 'sources': {
139 | 'positiveX' : skyBoxBaseUrl + 'px.jpg',
140 | 'negativeX' : skyBoxBaseUrl + 'mx.jpg',
141 | 'positiveY' : skyBoxBaseUrl + 'py.jpg',
142 | 'negativeY' : skyBoxBaseUrl + 'my.jpg',
143 | 'positiveZ' : skyBoxBaseUrl + 'pz.jpg',
144 | 'negativeZ' : skyBoxBaseUrl + 'mz.jpg'
145 | }
146 | });
147 | } else {
148 | this.scene.backgroundColor = new Cesium.Color(0, 0, 0, 0);
149 | }
150 |
151 | var primitives = this.scene.primitives;
152 |
153 | var ellipsoid = Cesium.Ellipsoid.WGS84;
154 | this.globe = new Cesium.Globe(ellipsoid);
155 |
156 | this.camera = new weapi.Camera(this.scene.camera, ellipsoid);
157 |
158 | this.scene.globe = this.globe;
159 |
160 | if (options['empty'] !== true) {
161 | // default layer -- OSM
162 | var secure = 'https:' == document.location.protocol;
163 | var protocol = (secure ? 'https:' : 'http:');
164 |
165 | var mq = new Cesium.OpenStreetMapImageryProvider({
166 | 'url': protocol + '//a.tile.openstreetmap.org/'
167 | });
168 | this.scene.imageryLayers.addImageryProvider(mq);
169 | }
170 | this.withTerrain = !!options['terrain'];
171 | if (this.withTerrain) {
172 | var url = options['terrain'];
173 | if (url === true) {
174 | // for compatibility
175 | url = 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles';
176 | }
177 | var terrainProvider = new Cesium.CesiumTerrainProvider({
178 | 'url': url,
179 | 'credit': options['terrainCredit']
180 | });
181 | this.scene.terrainProvider = terrainProvider;
182 | }
183 |
184 | /**
185 | * @type {!weapi.markers.MarkerManager}
186 | */
187 | this.markerManager = new weapi.markers.MarkerManager(this, container);
188 |
189 | /**
190 | * @type {!Array.}
191 | */
192 | this.composites = [];
193 |
194 | /**
195 | * @type {!Cesium.BillboardCollection}
196 | */
197 | this.polyIconCollection = new Cesium.BillboardCollection();
198 | primitives.add(this.polyIconCollection);
199 |
200 | var tick = goog.bind(function() {
201 | if (!this.forcedPause) {
202 | this.scene.initializeFrame(); // to update camera from animators and sscc
203 |
204 | // we need to use continous rendering with terrain --
205 | // we can't tell when the terrain data are finished loading and processing
206 | var renderNeeded = this.withTerrain || this.sceneChanged;
207 | this.sceneChanged = false;
208 | if (!renderNeeded) {
209 | // extended sceneChanged detection
210 | var viewMatrix = this.camera.camera.viewMatrix;
211 | if (!this.lastViewMatrix || !this.lastViewMatrix.equals(viewMatrix)) {
212 | viewMatrix.clone(this.lastViewMatrix);
213 | renderNeeded = true;
214 | }
215 | }
216 | if (renderNeeded) {
217 | //stats[0]++;
218 | this.scene.render();
219 | if (goog.isDefAndNotNull(this.miniglobe)) {
220 | this.miniglobe.draw();
221 | }
222 | this.markerManager.updateMarkers();
223 | if (goog.isDefAndNotNull(this.afterFrameOnce)) {
224 | this.afterFrameOnce();
225 | this.afterFrameOnce = null;
226 | }
227 | } else {
228 | //stats[1]++;
229 | }
230 | }
231 | Cesium.requestAnimationFrame(tick);
232 | }, this);
233 | Cesium.requestAnimationFrame(tick);
234 |
235 | var handler = new Cesium.ScreenSpaceEventHandler(this.canvas);
236 |
237 | var stopAnim = goog.bind(function() {this.camera.animator.cancel();}, this);
238 |
239 | handler.setInputAction(stopAnim, Cesium.ScreenSpaceEventType.LEFT_DOWN);
240 | handler.setInputAction(stopAnim, Cesium.ScreenSpaceEventType.RIGHT_DOWN);
241 | handler.setInputAction(stopAnim, Cesium.ScreenSpaceEventType.MIDDLE_DOWN);
242 | handler.setInputAction(stopAnim, Cesium.ScreenSpaceEventType.WHEEL);
243 | handler.setInputAction(stopAnim, Cesium.ScreenSpaceEventType.PINCH_START);
244 |
245 | goog.events.listen(window, 'resize', this.handleResize, false, this);
246 | this.handleResize();
247 |
248 |
249 | var pos = options['position'];
250 | var center = options['center'];
251 | if (goog.isDefAndNotNull(pos) && pos.length > 1) {
252 | this.camera.setPos(goog.math.toRadians(pos[0]),
253 | goog.math.toRadians(pos[1]),
254 | undefined);
255 | } else if (goog.isDefAndNotNull(center) && center.length > 1) {
256 | this.camera.setPos(goog.math.toRadians(center[0]),
257 | goog.math.toRadians(center[1]),
258 | undefined);
259 | }
260 |
261 | // TODO: zoom support
262 | var z = options['zoom'];
263 | if (goog.isDefAndNotNull(z)) window['console']['log']('zoom not supported');
264 |
265 | var alt = options['altitude'];
266 | if (goog.isDefAndNotNull(alt)) this.camera.setPos(undefined, undefined, alt);
267 |
268 | var sscc = this.scene.screenSpaceCameraController;
269 |
270 | if (options['unconstrainedRotation'] !== true) {
271 | this.scene.camera.constrainedAxis = Cesium.Cartesian3.UNIT_Z;
272 | }
273 |
274 | //sscc.enableLook = false;
275 | if (options['panning'] === false || options['dragging'] === false)
276 | sscc.enableRotate = false;
277 | if (options['tilting'] === false)
278 | sscc.enableTilt = false; //TODO: fix axis
279 | if (options['zooming'] === false || options['scrollWheelZoom'] === false)
280 | sscc.enableZoom = false;
281 |
282 | sscc.minimumZoomDistance = options['minAltitude'] || 20;
283 | sscc.maximumZoomDistance = options['maxAltitude'] || Infinity;
284 |
285 | sscc['tiltEventTypes'].push({
286 | 'eventType': Cesium['CameraEventType']['LEFT_DRAG'],
287 | 'modifier': Cesium['KeyboardEventModifier']['SHIFT']
288 | });
289 |
290 | sscc['lookEventTypes'] = {
291 | 'eventType': Cesium['CameraEventType']['LEFT_DRAG'],
292 | 'modifier': Cesium['KeyboardEventModifier']['ALT']
293 | };
294 |
295 | //HACK for color picking:
296 | // when Cesium creates texture from image, it discards original image.
297 | // It is however very unefficient to preform color picking on WebGLTexture.
298 | // Therefore, clone the image reference prior to creating the texture.
299 | var orig = Cesium['ImageryLayer'].prototype['_createTexture'];
300 | Cesium['ImageryLayer'].prototype['_createTexture'] = function(ctx, imgry) {
301 | imgry['__image__'] = imgry['image'];
302 | orig.call(this, ctx, imgry);
303 | };
304 |
305 | // + HACK for sceneChange detection after loading tiles:
306 | var that = this;
307 | var orig2 = Cesium['GlobeSurfaceTile']['processStateMachine'];
308 | Cesium['GlobeSurfaceTile']['processStateMachine'] =
309 | function(t, c, cl, tp, ic) {
310 | /*if (this['isRenderable']) */that.sceneChanged = true;
311 |
312 | orig2(t, c, cl, tp, ic);
313 | };
314 |
315 | setTimeout(function() {
316 | that.sceneChanged = true;
317 | }, 1);
318 | };
319 |
320 |
321 | /**
322 | * @define {number} Maximum size of the group of primitives;.
323 | */
324 | weapi.App.PRIMITIVE_GROUPING_SIZE = 10;
325 |
326 |
327 | /**
328 | * @param {HTMLCanvasElement=} opt_canvas
329 | * @param {Object=} opt_contextOpts
330 | * @return {?WebGLRenderingContext}
331 | */
332 | weapi.App.detectWebGLSupport = function(opt_canvas, opt_contextOpts) {
333 | if (!!window['WebGLRenderingContext']) {
334 | var canvas = opt_canvas || goog.dom.createElement('canvas'),
335 | names = ['webgl', 'experimental-webgl']; //'moz-webgl', 'webkit-3d'
336 | for (var i = 0; i < names.length; i++) {
337 | try {
338 | var ctx = /** @type {?WebGLRenderingContext} */
339 | (canvas.getContext(names[i], opt_contextOpts));
340 | if (ctx && goog.isFunction(ctx['getParameter'])) return ctx;
341 | } catch (e) {}
342 | }
343 | return null; // supported but disabled
344 | }
345 | return null; //not supported
346 | };
347 |
348 |
349 | /**
350 | * @param {Object} eventObj
351 | */
352 | weapi.App.prototype.listenCORSErrors = function(eventObj) {
353 | if (!this.CORSErrorReported) {
354 | eventObj['addEventListener'](function(e) {
355 | if (!this.CORSErrorReported) {
356 | //window['console']['log'](e);
357 | //if (e['timesRetried'] > 1) { // not an isolated network error
358 | if (this.isFileProtocol &&
359 | e['provider']['_url'].indexOf('http') !== 0) {
360 | alert('Tiles for WebGL must be accessed over http protocol.');
361 | } else {
362 | var msg = 'An error occured while accessing the tiles. Cross-domain' +
363 | ' access restrictions are applied on map tiles for WebGL. ' +
364 | 'Either use CORS on remote domain (http://enable-cors.org/) or ' +
365 | 'place your application on the same domain as tiles (hosting ' +
366 | 'app and tiles on the same domain or running a tile proxy).';
367 | if (window['console'] && window['console']['error']) {
368 | window['console']['error'](msg);
369 | }
370 | }
371 | this.CORSErrorReported = true; // report only once
372 | //}
373 | }
374 | }, this);
375 | }
376 | };
377 |
378 |
379 | /**
380 | * @param {!Cesium.BillboardCollection|!Cesium.PrimitiveCollection|
381 | * !Cesium.Polygon|!Cesium.PolylineCollection} object .
382 | */
383 | weapi.App.prototype.addPrimitive = function(object) {
384 | var composite = goog.array.findRight(this.composites, function(el, i, arr) {
385 | return el.length < weapi.App.PRIMITIVE_GROUPING_SIZE;
386 | });
387 |
388 | if (!composite) {
389 | composite = new Cesium.PrimitiveCollection();
390 | this.composites.push(composite);
391 | var primitives = this.scene.primitives;
392 | primitives.add(composite);
393 | primitives.raiseToTop(this.polyIconCollection);
394 | }
395 |
396 | composite.add(object);
397 | };
398 |
399 |
400 | /**
401 | * @param {!Cesium.BillboardCollection|!Cesium.PrimitiveCollection|
402 | * !Cesium.Polygon|!Cesium.PolylineCollection} object .
403 | */
404 | weapi.App.prototype.removePrimitive = function(object) {
405 | goog.array.forEach(this.composites, function(el, i, arr) {
406 | el['remove'](object);
407 | });
408 | };
409 |
410 |
411 | /**
412 | *
413 | */
414 | weapi.App.prototype.handleResize = function() {
415 | var width = this.canvas.clientWidth;
416 | var height = this.canvas.clientHeight;
417 |
418 | if (this.canvas.width === width && this.canvas.height === height) {
419 | return;
420 | }
421 |
422 | this.canvas.width = width;
423 | this.canvas.height = height;
424 | this.scene.camera.frustum.aspectRatio = width / height;
425 |
426 | this.sceneChanged = true;
427 | };
428 |
429 |
430 | /**
431 | * @param {!weapi.Map} map Map.
432 | */
433 | weapi.App.prototype.setBaseMap = function(map) {
434 | var layers = this.scene.imageryLayers;
435 | //this.scene.imageryLayers.get(0) = map.layer;
436 | layers.remove(layers.get(0), false);
437 | layers.add(map.layer, 0);
438 | map.app = this;
439 |
440 | this.sceneChanged = true;
441 | };
442 |
443 |
444 | /**
445 | * @param {weapi.Map} map Map.
446 | */
447 | weapi.App.prototype.setOverlayMap = function(map) {
448 | var length = this.scene.imageryLayers.length;
449 | var layers = this.scene.imageryLayers;
450 | if (length > 1) {
451 | layers.remove(layers.get(1), false);
452 | }
453 | if (goog.isDefAndNotNull(map)) {
454 | layers.add(map.layer);
455 | map.app = this;
456 | }
457 |
458 | this.sceneChanged = true;
459 | };
460 |
461 |
462 | /**
463 | * Register event listener.
464 | * @param {string} type Event type.
465 | * @param {function(Event)} listener Function to call back.
466 | * @return {goog.events.ListenableKey|null|number} listenKey.
467 | */
468 | weapi.App.prototype.on = function(type, listener) {
469 | /**
470 | * Wraps the listener function with a wrapper function
471 | * that adds some extended event info.
472 | * @param {!weapi.App} app .
473 | * @param {function(Event)} listener Original listener function.
474 | * @return {function(Event)} Wrapper listener.
475 | * @private
476 | */
477 | var wrap = function(app, listener) {
478 | return function(e) {
479 | e.target = app;
480 | e['latitude'] = null;
481 | e['longitude'] = null;
482 |
483 | var offsetX = e.offsetX, offsetY = e.offsetY;
484 | if (!goog.isDefAndNotNull(offsetX) || !goog.isDefAndNotNull(offsetY)) {
485 | var origE = e.getBrowserEvent();
486 | var pageX = origE.pageX, pageY = origE.pageY,
487 | touches = origE['touches'];
488 | if (touches && touches[0] && (!pageX || !pageY)) {
489 | pageX = touches[0].pageX;
490 | pageY = touches[0].pageY;
491 | }
492 | var canvasOffset = goog.style.getPageOffset(app.canvas);
493 | offsetX = pageX - canvasOffset.x;
494 | offsetY = pageY - canvasOffset.y;
495 | }
496 | if (goog.isDefAndNotNull(offsetX) && goog.isDefAndNotNull(offsetY)) {
497 | var cartesian = app.camera.camera.
498 | pickEllipsoid(new Cesium.Cartesian2(offsetX, offsetY));
499 | if (goog.isDefAndNotNull(cartesian)) {
500 | var carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(cartesian);
501 |
502 | var lat = goog.math.toDegrees(carto.latitude),
503 | lng = goog.math.toDegrees(carto.longitude);
504 | e['latlng'] = {'lat': lat, 'lng': lng};
505 | e['latitude'] = lat;
506 | e['longitude'] = lng;
507 | e['altitude'] = carto.height;
508 | e['originalEvent'] = e.getBrowserEvent();
509 | }
510 | }
511 |
512 | listener(e);
513 | };
514 | };
515 | var key = goog.events.listen(this.canvas, type, wrap(this, listener));
516 |
517 | listener[goog.getUid(this) + '___eventKey_' + type] = key;
518 |
519 | return key;
520 | };
521 |
522 |
523 | /**
524 | * Unregister event listener.
525 | * @param {string|number|null} typeOrKey Event type or listenKey.
526 | * @param {function(Event)} listener Function that was used to register.
527 | */
528 | weapi.App.prototype.off = function(typeOrKey, listener) {
529 | if (goog.isDefAndNotNull(listener)) {
530 | var key = listener[goog.getUid(this) + '___eventKey_' + typeOrKey];
531 | if (goog.isDefAndNotNull(key)) goog.events.unlistenByKey(key);
532 | } else if (!goog.isString(typeOrKey)) {
533 | goog.events.unlistenByKey(typeOrKey);
534 | }
535 | };
536 |
537 |
538 | /**
539 | * Unregister all event listeners of certain type.
540 | * @param {string} type Event type.
541 | */
542 | weapi.App.prototype.offAll = function(type) {
543 | goog.events.removeAll(this.canvas, type);
544 | };
545 |
546 |
547 | /**
548 | * @param {number|!weapi.markers.AbstractMarker} latOrMark Latitude or marker.
549 | * @param {number} lon Longitude.
550 | * @param {string=} opt_iconUrl URL of the icon to use instead of the default.
551 | * @param {number=} opt_width Width of the icon.
552 | * @param {number=} opt_height Height of the icon.
553 | * @return {!weapi.markers.AbstractMarker} New marker.
554 | */
555 | weapi.App.prototype.initMarker = function(latOrMark, lon,
556 | opt_iconUrl, opt_width, opt_height) {
557 | var mark;
558 | if (goog.isNumber(latOrMark)) {
559 | mark = new weapi.markers.PrettyMarker(goog.math.toRadians(latOrMark),
560 | goog.math.toRadians(lon),
561 | opt_iconUrl, opt_width, opt_height);
562 | } else {
563 | mark = latOrMark;
564 | }
565 |
566 | this.markerManager.addMarker(null, mark);
567 |
568 | this.sceneChanged = true;
569 |
570 | return mark;
571 | };
572 |
573 |
574 | /**
575 | * @param {!weapi.markers.AbstractMarker} marker .
576 | */
577 | weapi.App.prototype.removeMarker = function(marker) {
578 | this.markerManager.removeMarkerEx(marker);
579 |
580 | this.sceneChanged = true;
581 | };
582 |
583 |
584 | /**
585 | * @param {number} lat Latitude in radians.
586 | * @param {number} lng Longitude in radians.
587 | * @return {Array.} Pixel data
588 | * [r 0-255, g 0-255, b 0-255, a 0-1, zoomLevel].
589 | */
590 | weapi.App.prototype.getBestAvailablePixelColorFromLayer = function(lat, lng) {
591 | var layer = this.scene.imageryLayers.get(0);
592 | var provider = layer.imageryProvider;
593 | var scheme = provider.tilingScheme;
594 | var position = new Cesium.Cartographic(lng, lat);
595 |
596 | var result = null;
597 | var zoom = provider.maximumLevel;
598 |
599 | while (zoom >= 0) {
600 | var tileXY = scheme['positionToTileXY'](position, zoom);
601 | var imagery = layer['getImageryFromCache'](tileXY.x, tileXY.y,
602 | zoom, undefined);
603 | if (imagery['__image__']) {
604 | var pixelX = Math.floor((tileXY.x - Math.floor(tileXY.x)) *
605 | provider.tileWidth);
606 | var pixelY = Math.floor((tileXY.y - Math.floor(tileXY.y)) *
607 | provider.tileHeight);
608 |
609 | var canvas = goog.dom.createElement('canvas');
610 | canvas.width = 1;
611 | canvas.height = 1;
612 | var context = canvas.getContext('2d');
613 | context.drawImage(imagery['__image__'], pixelX, pixelY, 1, 1, 0, 0, 1, 1);
614 |
615 | var data = context.getImageData(0, 0, 1, 1).data;
616 |
617 | result = [data[0], data[1], data[2], data[3] / 255, zoom];
618 | }
619 | imagery['releaseReference']();
620 |
621 | if (result) return result;
622 |
623 | zoom--;
624 | }
625 |
626 | return [0, 0, 0, 0, -1];
627 | };
628 |
--------------------------------------------------------------------------------
/src/camera.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.Camera');
9 |
10 | goog.require('goog.dom');
11 |
12 | goog.require('weapi.CameraAnimator');
13 | goog.require('weapi.utils');
14 |
15 |
16 |
17 | /**
18 | *
19 | * @param {!Cesium.Camera} camera .
20 | * @param {Cesium.Ellipsoid} ellipsoid .
21 | * @constructor
22 | */
23 | weapi.Camera = function(camera, ellipsoid) {
24 | this.camera = camera;
25 | this.ellipsoid = ellipsoid;
26 | this.animator = new weapi.CameraAnimator(this);
27 | };
28 |
29 |
30 | /**
31 | * @return {Array.} [latitude, longitude, altitude].
32 | */
33 | weapi.Camera.prototype.getPos = function() {
34 | var carto = new Cesium.Cartographic(0, 0, 0);
35 | this.ellipsoid.cartesianToCartographic(this.camera.position, carto);
36 |
37 | return [carto.latitude, carto.longitude, carto.height];
38 | };
39 |
40 |
41 | /**
42 | * @param {number|undefined} latitude .
43 | * @param {number|undefined} longitude .
44 | * @param {number|undefined} altitude .
45 | */
46 | weapi.Camera.prototype.setPos = function(latitude, longitude, altitude) {
47 | if (!goog.isDef(latitude) ||
48 | !goog.isDef(longitude) ||
49 | !goog.isDef(altitude)) {
50 | var oldPos = this.getPos();
51 | latitude = goog.isDefAndNotNull(latitude) ? latitude : oldPos[0];
52 | longitude = goog.isDefAndNotNull(longitude) ? longitude : oldPos[1];
53 | altitude = altitude > 0 ? altitude : oldPos[2];
54 | }
55 | var pos = Cesium.Cartesian3.fromRadians(longitude, latitude, altitude);
56 | this.camera.setView({
57 | 'position': pos,
58 | 'heading': 0,
59 | 'pitch': -Math.PI / 2,
60 | 'roll': 0
61 | });
62 | };
63 |
64 |
65 | /**
66 | * @return {number} Heading in radians.
67 | */
68 | weapi.Camera.prototype.getHeading = function() {
69 | var camera = this.camera;
70 | var pos = camera.positionWC; //this forces the update
71 |
72 | var normal = new Cesium.Cartesian3(-pos.y, pos.x, 0);
73 | // = Cesium.Cartesian3.UNIT_Z.cross(pos).normalize();
74 | //var angle = (camera.right.angleBetween(normal.normalize()));
75 | var angle = Cesium.Cartesian3.angleBetween(camera.right, normal);
76 | var orientation = Cesium.Cartesian3.cross(pos, camera.up,
77 | new Cesium.Cartesian3()).z;
78 |
79 | return (orientation < 0 ? angle : -angle);
80 | };
81 |
82 |
83 | /**
84 | * @return {number} Tilt in radians.
85 | */
86 | weapi.Camera.prototype.getTilt = function() {
87 | var camera = this.camera;
88 | var pos = camera.positionWC; //this forces the update
89 |
90 | var angle = Math.acos(Cesium.Cartesian3.dot(camera.up,
91 | Cesium.Cartesian3.normalize(pos, new Cesium.Cartesian3())));
92 |
93 | return -angle + Math.PI / 2;
94 | };
95 |
96 |
97 | /**
98 | * @param {number} heading .
99 | */
100 | weapi.Camera.prototype.setHeading = function(heading) {
101 | var heading_, tilt_ = this.getTilt();
102 |
103 | heading_ = heading - this.getHeading();
104 | this.camera.lookDown(tilt_);
105 | this.camera.twistLeft(heading_);
106 | this.camera.lookUp(tilt_);
107 | };
108 |
109 |
110 | /**
111 | * @param {number} tilt .
112 | */
113 | weapi.Camera.prototype.setTilt = function(tilt) {
114 | var tilt_ = tilt - this.getTilt();
115 |
116 | var heading_ = this.getHeading();
117 | this.camera.lookUp(tilt_);
118 | this.setHeading(heading_); //re-set the heading
119 | };
120 |
121 |
122 | /**
123 | * @param {number} heading .
124 | * @param {number} tilt .
125 | */
126 | weapi.Camera.prototype.setHeadingAndTilt = function(heading, tilt) {
127 | var heading_, tilt_ = this.getTilt();
128 |
129 | heading_ = heading - this.getHeading();
130 | this.camera.lookDown(tilt_);
131 | this.camera.twistLeft(heading_);
132 | this.camera.lookUp(tilt);
133 | };
134 |
135 |
136 | /**
137 | * The most effective way to set complete camera position.
138 | * @param {number} lat .
139 | * @param {number} lng .
140 | * @param {number} alt .
141 | * @param {number} heading .
142 | * @param {number} tilt .
143 | */
144 | weapi.Camera.prototype.setPosHeadingAndTilt = function(lat, lng, alt,
145 | heading, tilt) {
146 | var pos = Cesium.Cartesian3.fromRadians(lng, lat, alt);
147 | this.camera.setView({
148 | 'position': pos,
149 | 'heading': 0,
150 | 'pitch': -Math.PI / 2,
151 | 'roll': 0
152 | });
153 | this.camera.twistLeft(heading);
154 | this.camera.lookUp(tilt);
155 | };
156 |
157 |
158 | /**
159 | * Calculates at what distance should given bounds be view to fit on screen.
160 | * @param {number} minlat .
161 | * @param {number} maxlat .
162 | * @param {number} minlon .
163 | * @param {number} maxlon .
164 | * @return {number} Proposed distance.
165 | */
166 | weapi.Camera.prototype.calcDistanceToViewBounds = function(minlat, maxlat,
167 | minlon, maxlon) {
168 | var centerLat = (minlat + maxlat) / 2;
169 |
170 | var distEW = weapi.utils.calculateDistance(centerLat, minlon,
171 | centerLat, maxlon);
172 |
173 | var distNS = weapi.utils.calculateDistance(minlat, 0,
174 | maxlat, 0);
175 |
176 | var aspectR =
177 | Math.min(Math.max(this.camera.frustum.aspectRatio, distEW / distNS), 1.0);
178 |
179 | // Create a LookAt using the experimentally derived distance formula.
180 | var alpha =
181 | goog.math.toRadians(goog.math.toDegrees(this.camera.frustum.fovy) /
182 | (aspectR + 0.4) - 2.0);
183 | var expandToDistance = Math.max(distNS, distEW);
184 |
185 | var beta =
186 | Math.min(Math.PI / 2,
187 | alpha + expandToDistance / (2 * weapi.utils.EARTH_RADIUS));
188 |
189 | var lookAtRange = 1.5 * weapi.utils.EARTH_RADIUS *
190 | (Math.sin(beta) * Math.sqrt(1 + 1 / Math.pow(Math.tan(alpha), 2)) - 1);
191 |
192 | return lookAtRange;
193 | };
194 |
195 |
196 | /**
197 | * Calculates such coordinates of the camera, that when we place
198 | * the camera there with specified heading and tilt the original
199 | * [lat, lng] is in the center of the view and at specified distance.
200 | * @param {number} lat Latitude in radians.
201 | * @param {number} lng Longitude in radians.
202 | * @param {number} alt Altitude in meters.
203 | * @param {number=} opt_heading Heading in radians (otherwise 0).
204 | * @param {number=} opt_tilt Tilt in radians (otherwise 0).
205 | * @return {!Array.} Array [lat, lng] in radians.
206 | */
207 | weapi.Camera.calculatePositionForGivenTarget = function(lat, lng, alt,
208 | opt_heading,
209 | opt_tilt) {
210 | alt /= weapi.utils.EARTH_RADIUS;
211 | var innerAngle = Math.PI - (opt_tilt || 0);
212 |
213 | // we have the following triangle:
214 | // side a=dist (desired distance of camera from the target)
215 | // side b=1 (Earth radius)
216 | // side c=1 + alt
217 | // angle gamma=innerAngle (between a and b)
218 | // Using the law of sines we can calculate beta:
219 | // sin(beta) / b = sin(gamma) / c
220 | var c = 1 + alt;
221 | var beta = Math.asin(Math.sin(innerAngle) / c);
222 | var alpha = Math.PI - beta - innerAngle;
223 |
224 | var head = opt_heading || 0;
225 |
226 | return [lat - Math.cos(head) * alpha, lng + Math.sin(head) * alpha];
227 | };
228 |
229 |
230 | /**
231 | * Calculates altitude from zoom (very rough approximation for deprecated API)
232 | * @param {!HTMLCanvasElement} canvas .
233 | * @param {number} fov Vertical fov in radians.
234 | * @param {number} zoom Zoom.
235 | * @param {number} latitude Latitude in radians.
236 | * @return {number} Calculated altitude.
237 | */
238 | weapi.Camera.calcAltitudeForZoom = function(canvas, fov, zoom, latitude) {
239 | // 0.7 is old constant from WebGL Earth, 256 is tile size
240 | var tilesVertically = 0.7 * canvas.height / 256;
241 |
242 | var o = Math.cos(Math.abs(latitude)) * 2 * Math.PI;
243 | var thisPosDeformation = o / Math.pow(2, zoom);
244 | var sizeIWannaSee = thisPosDeformation * tilesVertically;
245 | return (1 / Math.tan(fov / 2)) * (sizeIWannaSee / 2) *
246 | weapi.utils.EARTH_RADIUS;
247 | };
248 |
249 |
250 | /**
251 | * Calculates zoom from altitude (very rough approximation for deprecated API)
252 | * @param {!HTMLCanvasElement} canvas .
253 | * @param {number} fov Vertical fov in radians.
254 | * @param {number} altitude Altitude in meters.
255 | * @param {number} latitude Latitude in radians.
256 | * @return {number} Calculated zoom.
257 | */
258 | weapi.Camera.calcZoomForAltitude = function(canvas, fov, altitude, latitude) {
259 | // 0.7 is old constant from WebGL Earth, 256 is tile size
260 | var tilesVertically = 0.7 * canvas.height / 256;
261 |
262 | var sizeISee = 2 * (altitude / weapi.utils.EARTH_RADIUS) * Math.tan(fov / 2);
263 | var sizeOfOneTile = sizeISee / tilesVertically;
264 | var o = Math.cos(Math.abs(latitude)) * 2 * Math.PI;
265 |
266 | return Math.log(o / sizeOfOneTile) / Math.LN2;
267 | };
268 |
--------------------------------------------------------------------------------
/src/cameraanimator.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.CameraAnimator');
9 |
10 | goog.require('goog.events');
11 | goog.require('goog.fx.Animation');
12 | goog.require('goog.fx.Animation.EventType');
13 | goog.require('goog.fx.AnimationSerialQueue');
14 | goog.require('goog.fx.Transition.EventType');
15 | goog.require('goog.math');
16 |
17 | goog.require('weapi.utils');
18 |
19 |
20 |
21 | /**
22 | * @param {!weapi.Camera} camera Camera to be animated by this object.
23 | * @constructor
24 | */
25 | weapi.CameraAnimator = function(camera) {
26 |
27 | /**
28 | * @type {!weapi.Camera}
29 | * @private
30 | */
31 | this.camera_ = camera;
32 |
33 | /**
34 | * @type {goog.fx.AnimationSerialQueue}
35 | * @private
36 | */
37 | this.animation_ = null;
38 | };
39 |
40 |
41 | /**
42 | * @define {number} Animation duration in seconds.
43 | */
44 | weapi.CameraAnimator.CAMERA_ANIMATION_DURATION = 3;
45 |
46 |
47 | /**
48 | * @define {number} How much can the camera climb (or descent)
49 | * (in meters of altitude per meters of surface distance).
50 | */
51 | weapi.CameraAnimator.CAMERA_ANIMATION_MAX_ASCENT = 0.2;
52 |
53 |
54 | /**
55 | * TODO: use CameraFlightPath ?
56 | * Initiate and start the animation to given location.
57 | * @param {number} latitude Latitude in radians.
58 | * @param {number} longitude Longitude in radians.
59 | * @param {number=} opt_altitude Altitude (otherwise unchanged).
60 | * @param {number=} opt_heading Heading (otherwise 0).
61 | * @param {number=} opt_tilt Tilt (otherwise 0).
62 | * @param {boolean=} opt_targetPosition If true, the camera is position in
63 | * such a way, that the camera target is
64 | * [latitude, longitude] (default false).
65 | * @param {number=} opt_duration Duration of the animation in seconds.
66 | */
67 | weapi.CameraAnimator.prototype.flyTo = function(latitude, longitude,
68 | opt_altitude,
69 | opt_heading, opt_tilt,
70 | opt_targetPosition,
71 | opt_duration) {
72 | var cam = this.camera_;
73 |
74 | if (opt_targetPosition) {
75 | var newPos = weapi.Camera.calculatePositionForGivenTarget(
76 | latitude, longitude, opt_altitude || cam.getPos()[2],
77 | opt_heading, opt_tilt);
78 |
79 | latitude = newPos[0];
80 | longitude = newPos[1];
81 | }
82 |
83 | if (goog.isDefAndNotNull(this.animation_)) {
84 | this.onEnd_();
85 | }
86 |
87 | var srcPos = cam.getPos();
88 | var curAlt = srcPos[2];
89 | var dstAlt = opt_altitude || curAlt;
90 |
91 | // Validates start and end location so that
92 | // the animation goes through the shortest path.
93 | var lonStart = goog.math.modulo(srcPos[1], 2 * Math.PI);
94 | var lonEnd = goog.math.modulo(longitude, 2 * Math.PI);
95 |
96 | var lonDiff = lonStart - lonEnd;
97 | if (lonDiff < -Math.PI) {
98 | lonStart += 2 * Math.PI;
99 | } else if (lonDiff > Math.PI) {
100 | lonEnd += 2 * Math.PI;
101 | }
102 |
103 |
104 | var start_ = [srcPos[0], lonStart, curAlt, cam.getHeading(), cam.getTilt()];
105 | var end_ = [latitude, lonEnd, dstAlt, opt_heading || 0, opt_tilt || 0];
106 |
107 | //in-out quintic
108 | var supereasing = function(t) {
109 | var t2 = t * t;
110 | var t3 = t * t * t;
111 | return 6 * t3 * t2 + -15 * t2 * t2 + 10 * t3;
112 | };
113 | var supereasing_f = function(t) {return 2 * supereasing(t / 2);};
114 | var halfVal = supereasing(0.5);
115 | var supereasing_l = function(t) {
116 | return (supereasing(0.5 + t / 2) - halfVal) / (1 - halfVal);
117 | };
118 |
119 | var animationAlteringEvents = [goog.fx.Transition.EventType.BEGIN,
120 | goog.fx.Animation.EventType.ANIMATE,
121 | goog.fx.Transition.EventType.END,
122 | goog.fx.Transition.EventType.FINISH];
123 |
124 | this.animation_ = new goog.fx.AnimationSerialQueue();
125 |
126 | var duration =
127 | 1000 * (opt_duration || weapi.CameraAnimator.CAMERA_ANIMATION_DURATION);
128 |
129 | if (opt_altitude) {
130 | var distance = weapi.utils.calculateDistance(srcPos[0], srcPos[1],
131 | latitude, longitude);
132 |
133 | var topPoint = Math.min(curAlt, dstAlt) +
134 | distance * weapi.CameraAnimator.CAMERA_ANIMATION_MAX_ASCENT;
135 |
136 | //Don't allow the topPoint to be lower than highest of the two points
137 | if (topPoint < Math.max(curAlt, dstAlt)) {
138 | topPoint = (curAlt + dstAlt) / 2;
139 | }
140 |
141 | var top = [];
142 | top[0] = (start_[0] + end_[0]) / 2;
143 | top[1] = (start_[1] + end_[1]) / 2;
144 | top[2] = topPoint;
145 | top[3] = (start_[3] + end_[3]) / 2;
146 | top[4] = (start_[4] + end_[4]) / 2;
147 |
148 | var ascentAnim = new goog.fx.Animation(start_, top,
149 | duration / 2, supereasing_f);
150 |
151 | var descentAnim = new goog.fx.Animation(top, end_,
152 | duration / 2, supereasing_l);
153 |
154 | goog.events.listen(ascentAnim, animationAlteringEvents,
155 | this.onEverythingAnimate_, false, this);
156 |
157 | goog.events.listen(descentAnim, animationAlteringEvents,
158 | this.onEverythingAnimate_, false, this);
159 |
160 | this.animation_.add(ascentAnim);
161 | this.animation_.add(descentAnim);
162 | } else {
163 | //single animation when altitude is not changing
164 | var anim = new goog.fx.Animation(start_, end_, duration, supereasing);
165 |
166 | goog.events.listen(anim, animationAlteringEvents,
167 | this.onEverythingAnimate_, false, this);
168 |
169 | this.animation_.add(anim);
170 | }
171 |
172 |
173 | goog.events.listen(this.animation_, goog.fx.Transition.EventType.END,
174 | this.onEnd_, false, this);
175 |
176 | this.animation_.play();
177 | };
178 |
179 |
180 | /**
181 | * Animate everything except altitude
182 | * @param {goog.events.Event} e The event.
183 | * @private
184 | */
185 | weapi.CameraAnimator.prototype.onEverythingAnimate_ = function(e) {
186 | this.camera_.setPosHeadingAndTilt(e.coords[0], e.coords[1], e.coords[2],
187 | e.coords[3], e.coords[4]);
188 | };
189 |
190 |
191 | /**
192 | * Called when animation ends
193 | * @private
194 | */
195 | weapi.CameraAnimator.prototype.onEnd_ = function() {
196 | if (goog.isDefAndNotNull(this.animation_)) {
197 | this.animation_.dispose();
198 | this.animation_ = null;
199 | }
200 | };
201 |
202 |
203 | /**
204 | * If the animation is in progress, cancel it
205 | */
206 | weapi.CameraAnimator.prototype.cancel = function() {
207 | this.onEnd_();
208 | };
209 |
--------------------------------------------------------------------------------
/src/canvas2image.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 Klokan Technologies GmbH (info@klokantech.com)
3 | *
4 | * The JavaScript code in this page is free software: you can
5 | * redistribute it and/or modify it under the terms of the GNU
6 | * General Public License (GNU GPL) as published by the Free Software
7 | * Foundation, either version 3 of the License, or (at your option)
8 | * any later version. The code is distributed WITHOUT ANY WARRANTY;
9 | * without even the implied warranty of MERCHANTABILITY or FITNESS
10 | * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
11 | *
12 | * USE OF THIS CODE OR ANY PART OF IT IN A NONFREE SOFTWARE IS NOT ALLOWED
13 | * WITHOUT PRIOR WRITTEN PERMISSION FROM KLOKAN TECHNOLOGIES GMBH.
14 | *
15 | * As additional permission under GNU GPL version 3 section 7, you
16 | * may distribute non-source (e.g., minimized or compacted) forms of
17 | * that code without the copy of the GNU GPL normally required by
18 | * section 4, provided you include this license notice and a URL
19 | * through which recipients can access the Corresponding Source.
20 | *
21 | */
22 |
23 | /**
24 | * @fileoverview Serves for extended saving of canvas content as png.
25 | *
26 | * @author petr.sloup@klokantech.com (Petr Sloup)
27 | *
28 | */
29 |
30 | goog.provide('we.canvas2image');
31 |
32 |
33 | // Modified version of
34 | // http://purl.eligrey.com/github/canvas-toBlob.js/blob/master/canvas-toBlob.js
35 | (function(view) {
36 | 'use strict';
37 | var is_base64_regex = /\s*;\s*base64\s*(?:;|$)/i,
38 | base64_ranks = new Uint8Array([
39 | 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1,
40 | -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
41 | 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
42 | -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
43 | 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
44 | ]),
45 | decode_base64 = function(base64) {
46 | var len = base64.length,
47 | buffer = new Uint8Array(len / 4 * 3 | 0),
48 | i = 0,
49 | outptr = 0,
50 | last = [0, 0],
51 | state = 0,
52 | save = 0;
53 |
54 | while (len--) {
55 | var code = base64.charCodeAt(i++);
56 | var rank = base64_ranks[code - 43];
57 | if (rank !== 255 && rank !== undefined) {
58 | last[1] = last[0];
59 | last[0] = code;
60 | save = (save << 6) | rank;
61 | state++;
62 | if (state === 4) {
63 | buffer[outptr++] = save >>> 16;
64 | if (last[1] !== 61 /* padding character */) {
65 | buffer[outptr++] = save >>> 8;
66 | }
67 | if (last[0] !== 61 /* padding character */) {
68 | buffer[outptr++] = save;
69 | }
70 | state = 0;
71 | }
72 | }
73 | }
74 | // 2/3 chance there's going to be some null bytes at the end, but that
75 | // doesn't really matter with most image formats.
76 | // If it somehow matters for you, truncate the buffer up outptr.
77 | return buffer.buffer;
78 | };
79 |
80 | if (HTMLCanvasElement && !HTMLCanvasElement.prototype.toBlob) {
81 | HTMLCanvasElement.prototype.toBlob = function(callback, type) {
82 | if (!type) {
83 | type = 'image/png';
84 | } if (this['mozGetAsFile']) {
85 | callback(this['mozGetAsFile']('canvas', type));
86 | return;
87 | }
88 | var args = Array.prototype.slice.call(arguments, 1),
89 | dataURI = this.toDataURL.apply(this, args),
90 | header_end = dataURI.indexOf(','),
91 | data = dataURI.substring(header_end + 1),
92 | is_base64 = is_base64_regex.test(dataURI.substring(0, header_end)),
93 | BlobBuilder = view['BlobBuilder'] ||
94 | view['WebKitBlobBuilder'] || view['MozBlobBuilder'],
95 | bb = new BlobBuilder;
96 |
97 | if (is_base64) {
98 | bb.append(decode_base64(data));
99 | } else {
100 | bb.append(decodeURIComponent(data));
101 | }
102 |
103 | callback(bb.getBlob(type));
104 | };
105 | }
106 | }(self));
107 |
108 | // Modified version of (stripped IE support and general cleaning):
109 | // http://hackworthy.blogspot.cz/2012/05/savedownload-data-generated-in.html
110 | var showSave_;
111 | var BlobBuilder_ = window['BlobBuilder'] ||
112 | window['WebKitBlobBuilder'] || window['MozBlobBuilder'];
113 | var URL_ = window['URL'] || window['webkitURL'] || window['mozURL'];
114 | var saveBlob_ = navigator['saveBlob'] ||
115 | navigator['mozSaveBlob'] || navigator['webkitSaveBlob'];
116 | var saveAs_ = window['saveAs'] || window['webkitSaveAs'] || window['mozSaveAs'];
117 |
118 | if (BlobBuilder_ && (saveAs_ || saveBlob_)) {
119 | showSave_ = function(data, name, mimetype) {
120 | var builder = new BlobBuilder_();
121 | builder.append(data);
122 | var blob = builder['getBlob'](mimetype || 'application/octet-stream');
123 | if (!name) name = 'Download.bin';
124 | if (saveAs_) {
125 | saveAs_(blob, name);
126 | } else {
127 | saveBlob_(blob, name);
128 | }
129 | };
130 | } else if (BlobBuilder_ && URL_) {
131 | showSave_ = function(data, name, mimetype) {
132 | var blob, url, builder = new BlobBuilder_();
133 | builder.append(data);
134 | if (!mimetype) mimetype = 'application/octet-stream';
135 | if ('download' in document.createElement('a')) {
136 | blob = builder['getBlob'](mimetype);
137 | url = URL_['createObjectURL'](blob);
138 | var link = document.createElement('a');
139 | link.setAttribute('href', url);
140 | link.setAttribute('download', name || 'Download.bin');
141 | var event = document.createEvent('MouseEvents');
142 | event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0,
143 | false, false, false, false, 0, null);
144 | link.dispatchEvent(event);
145 | } else {
146 | // mime types that don't trigger a download when opened in a browser:
147 | var BrowserSupportedMimeTypes = {
148 | 'image/jpeg': true,
149 | 'image/png': true,
150 | 'image/gif': true,
151 | 'image/svg+xml': true,
152 | 'image/bmp': true,
153 | 'image/x-windows-bmp': true,
154 | 'image/webp': true,
155 | 'audio/wav': true,
156 | 'audio/mpeg': true,
157 | 'audio/webm': true,
158 | 'audio/ogg': true,
159 | 'video/mpeg': true,
160 | 'video/webm': true,
161 | 'video/ogg': true,
162 | 'text/plain': true,
163 | 'text/html': true,
164 | 'text/xml': true,
165 | 'application/xhtml+xml': true,
166 | 'application/json': true
167 | };
168 | if (BrowserSupportedMimeTypes[mimetype.split(';')[0]] === true) {
169 | mimetype = 'application/octet-stream';
170 | }
171 | blob = builder['getBlob'](mimetype);
172 | url = URL_['createObjectURL'](blob);
173 | window.open(url, '_blank', '');
174 | }
175 | setTimeout(function() {
176 | URL_['revokeObjectURL'](url);
177 | }, 250);
178 | };
179 | }
180 |
181 |
182 | /**
183 | *
184 | * @param {!HTMLCanvasElement} canvas .
185 | * @param {weapi.markers.MarkerManager=} opt_markerMgr .
186 | * @param {weapi.MiniGlobe=} opt_miniGlobe .
187 | * @return {!HTMLCanvasElement} Canvas with possibly additional content.
188 | */
189 | we.canvas2image.prepareCanvas = function(canvas, opt_markerMgr, opt_miniGlobe) {
190 | var canvas_ = canvas;
191 | if (opt_markerMgr || opt_miniGlobe) {
192 | canvas_ = goog.dom.createElement('canvas');
193 | canvas_.width = canvas.width;
194 | canvas_.height = canvas.height;
195 | var ctx = /** @type {!CanvasRenderingContext2D} */
196 | (canvas_.getContext('2d'));
197 | ctx.drawImage(canvas, 0, 0);
198 | if (opt_markerMgr) {
199 | opt_markerMgr.forEach(function(marker) {
200 | marker.draw2D(ctx);
201 | });
202 | }
203 | if (opt_miniGlobe) {
204 | opt_miniGlobe.drawToCanvas2D(ctx);
205 | }
206 | }
207 | return /** @type {!HTMLCanvasElement} */(canvas_);
208 | };
209 |
210 |
211 | /**
212 | * Offers the user to save the png image
213 | * as if it was a regular download from the server.
214 | * Custom filename may not be supported by the browser.
215 | * @param {!HTMLCanvasElement} canvas .
216 | * @param {string} filename .
217 | */
218 | we.canvas2image.saveCanvasAsPNG = function(canvas, filename) {
219 | if (showSave_ && canvas.toBlob) {
220 | canvas.toBlob(function(blob) {
221 | showSave_(blob, filename, 'image/png');
222 | }, 'image/png');
223 | } else if (canvas.toDataURL) {
224 | var strData = canvas.toDataURL();
225 | document.location.href = strData.replace('image/png', 'image/octet-stream');
226 | }
227 | };
228 |
229 |
230 | /**
231 | * @param {!HTMLCanvasElement} canvas .
232 | * @return {string} 'data:image/png...' representation of the canvas content.
233 | */
234 | we.canvas2image.getCanvasAsDataURL = function(canvas) {
235 | return canvas.toDataURL ? canvas.toDataURL() : 'data:image/png,';
236 | };
237 |
--------------------------------------------------------------------------------
/src/custommap.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.CustomMap');
9 |
10 |
11 |
12 | /**
13 | * @param {Object.} opts .
14 | * @extends {Cesium.TileMapServiceImageryProvider}
15 | * @constructor
16 | */
17 | weapi.CustomMap = function(opts) {
18 | this.setOptions(opts);
19 | };
20 | goog.inherits(weapi.CustomMap, Cesium.TileMapServiceImageryProvider);
21 |
22 |
23 | /**
24 | * @param {Object.} opts .
25 | */
26 | weapi.CustomMap.prototype.setOptions = function(opts) {
27 | this['_url'] = /** @type {string} */(opts['url']);
28 |
29 | this['_minimumLevel'] = /** @type {number} */(opts['minimumLevel'] || 0);
30 | this['_maximumLevel'] = /** @type {number} */(opts['maximumLevel'] || 18);
31 |
32 | this.tileSize = /** @type {number} */(opts['tileSize'] || 256);
33 | this['_tileWidth'] = this['_tileHeight'] = this.tileSize;
34 |
35 | var b = opts['bounds'], rectangle = null;
36 | if (b && b.length && b.length > 3) {
37 | rectangle = new Cesium.Rectangle(goog.math.toRadians(b[0]),
38 | goog.math.toRadians(b[1]),
39 | goog.math.toRadians(b[2]),
40 | goog.math.toRadians(b[3]));
41 | }
42 |
43 | this.flipY = /** @type {boolean} */(opts['flipY'] || false);
44 |
45 | this.subdomains = /** @type {Array.} */(opts['subdomains'] || []);
46 |
47 | this['_credit'] = new Cesium.Credit((opts['copyright'] || '').toString(),
48 | undefined, opts['copyrightLink'] ? opts['copyrightLink'].toString() : undefined);
49 |
50 | this['_proxy'] = opts['proxy'] || undefined;
51 |
52 | this.emptyTile = goog.dom.createElement('canvas');
53 | this.emptyTile.width = this.tileSize;
54 | this.emptyTile.height = this.tileSize;
55 |
56 | this['_errorEvent'] = new Cesium.Event();
57 | this['_tilingScheme'] = new Cesium.WebMercatorTilingScheme();
58 | this['_rectangle'] = rectangle || this['_tilingScheme']['rectangle'];
59 | this['_ready'] = goog.isDefAndNotNull(this['_url']);
60 | };
61 |
62 |
63 | /**
64 | * @param {number} zoom .
65 | * @param {number} x .
66 | * @param {number} y .
67 | * @return {string} .
68 | */
69 | weapi.CustomMap.prototype.buildTileURL = function(zoom, x, y) {
70 | /** @type {string} */
71 | var url = this.url.replace('{z}', zoom.toFixed(0));
72 | url = url.replace('{x}', x.toFixed(0));
73 | url = url.replace('{y}', (this.flipY ? ((1 << zoom) - y - 1) : y).toFixed(0));
74 | if (this.subdomains.length > 0) {
75 | var subid = goog.math.modulo(x + y + zoom, this.subdomains.length);
76 | url = url.replace('{sub}', this.subdomains[subid]);
77 | }
78 | return this.proxy ? this.proxy['getURL'](url) : url;
79 | };
80 |
81 |
82 | /**
83 | * @param {number} x .
84 | * @param {number} y .
85 | * @param {number} level .
86 | * @return {Object} .
87 | * @this {weapi.CustomMap}
88 | */
89 | weapi.CustomMap.prototype['requestImage'] = function(x, y, level) {
90 | if (level < this.minimumLevel ||
91 | level > this.maximumLevel) return this.emptyTile;
92 | var url = this.buildTileURL(level, x, y);
93 | return Cesium.ImageryProvider.loadImage(this, url);
94 | };
95 |
--------------------------------------------------------------------------------
/src/editablepolygon.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.EditablePolygon');
9 |
10 | goog.require('goog.color');
11 |
12 | goog.require('weapi.PolyIcon');
13 | goog.require('weapi.Polygon');
14 | goog.require('weapi.markers.PolyDragger');
15 |
16 |
17 |
18 | /**
19 | * @param {!weapi.App} app .
20 | * @param {!weapi.markers.MarkerManager} markermanager MarkerManager to use.
21 | * @constructor
22 | */
23 | weapi.EditablePolygon = function(app, markermanager) {
24 | /**
25 | * @type {!weapi.App}
26 | * @protected
27 | */
28 | this.app = app;
29 |
30 | /**
31 | * @type {!weapi.markers.MarkerManager}
32 | * @private
33 | */
34 | this.markermanager_ = markermanager;
35 |
36 | /**
37 | * @type {!weapi.Polygon}
38 | * @private
39 | */
40 | this.polygon_ = new weapi.Polygon();
41 |
42 | this.app.addPrimitive(this.polygon_.primitive);
43 | this.app.addPrimitive(this.polygon_.primitiveLineCol);
44 | this.app.sceneChanged = true;
45 |
46 | /**
47 | * @type {!Object.}
48 | * @private
49 | */
50 | this.draggers_ = {};
51 |
52 | /**
53 | * @type {!Object.}
54 | * @private
55 | */
56 | this.midMap_ = {};
57 |
58 | /**
59 | * @type {!Object.}
60 | * @private
61 | */
62 | this.midDraggers_ = {};
63 |
64 | /**
65 | * @type {goog.events.ListenableKey|null|number}
66 | * @private
67 | */
68 | this.clickListenKey_ = null;
69 |
70 | /**
71 | * @type {!weapi.PolyIcon}
72 | * @private
73 | */
74 | this.icon_ = new weapi.PolyIcon(0, 0, this.app);
75 | //this.icon_.setImage('47.png', 100);
76 |
77 | /**
78 | * @type {!function()}
79 | * @private
80 | */
81 | this.onchange_ = goog.nullFunction;
82 |
83 | /**
84 | * @type {number}
85 | * @private
86 | */
87 | this.lastClickToAdd_ = 0;
88 | };
89 |
90 |
91 | /**
92 | */
93 | weapi.EditablePolygon.prototype.destroy = function() {
94 | this.disableClickToAdd();
95 | this.app.removePrimitive(this.polygon_.primitive);
96 | this.app.removePrimitive(this.polygon_.primitiveLineCol);
97 | this.app.sceneChanged = true;
98 | this.onchange_ = goog.nullFunction;
99 | this.icon_.destroy();
100 | goog.object.forEach(this.midDraggers_, function(el, key, obj) {
101 | this.markermanager_.removeMarker(el);
102 | }, this);
103 | goog.object.forEach(this.draggers_, function(el, key, obj) {
104 | this.markermanager_.removeMarker(el);
105 | }, this);
106 | delete this.midMap_;
107 | delete this.polygon_;
108 | };
109 |
110 |
111 | /**
112 | */
113 | weapi.EditablePolygon.prototype.enableClickToAdd = function() {
114 | if (goog.isDefAndNotNull(this.clickListenKey_)) return;
115 | // when mouse is down, wait for mouseup and check, if it wasn't a dragging..
116 | this.clickListenKey_ = goog.events.listen(this.app.canvas,
117 | goog.events.EventType.MOUSEDOWN, function(e) {
118 | goog.events.listenOnce(this.app.canvas,
119 | goog.events.EventType.MOUSEUP, function(e_) {
120 | if (e_.button == 0 && !goog.isNull(this.clickListenKey_)) {
121 | if (Math.max(Math.abs(e.offsetX - e_.offsetX),
122 | Math.abs(e.offsetY - e_.offsetY)) <= 3) {
123 | var cartesian = this.app.camera.camera.pickEllipsoid(
124 | new Cesium.Cartesian2(e_.offsetX, e_.offsetY));
125 | if (goog.isDefAndNotNull(cartesian)) {
126 | var carto = Cesium.Ellipsoid.WGS84.
127 | cartesianToCartographic(cartesian);
128 | this.addPoint(goog.math.toDegrees(carto.latitude),
129 | goog.math.toDegrees(carto.longitude));
130 | e_.preventDefault();
131 | this.lastClickToAdd_ = goog.now();
132 | }
133 | }
134 | }
135 | }, false, this);
136 | }, false, this);
137 | };
138 |
139 |
140 | /**
141 | */
142 | weapi.EditablePolygon.prototype.disableClickToAdd = function() {
143 | goog.events.unlistenByKey(this.clickListenKey_);
144 | this.clickListenKey_ = null;
145 | };
146 |
147 |
148 | /**
149 | * @param {string} hexColor #rrggbb.
150 | * @param {number=} opt_a [0-1], defaults to 1.
151 | */
152 | weapi.EditablePolygon.prototype.setFillColor = function(hexColor, opt_a) {
153 | hexColor = goog.color.normalizeHex(hexColor);
154 | var r = parseInt(hexColor.substr(1, 2), 16) / 255;
155 | var g = parseInt(hexColor.substr(3, 2), 16) / 255;
156 | var b = parseInt(hexColor.substr(5, 2), 16) / 255;
157 |
158 | this.polygon_.primitive.material.uniforms['color'] =
159 | new Cesium.Color(r, g, b, opt_a);
160 |
161 | this.app.sceneChanged = true;
162 | };
163 |
164 |
165 | /**
166 | * @param {string} hexColor #rrggbb.
167 | * @param {number=} opt_a [0-1], defaults to 1.
168 | */
169 | weapi.EditablePolygon.prototype.setStrokeColor = function(hexColor, opt_a) {
170 | hexColor = goog.color.normalizeHex(hexColor);
171 | var r = parseInt(hexColor.substr(1, 2), 16) / 255;
172 | var g = parseInt(hexColor.substr(3, 2), 16) / 255;
173 | var b = parseInt(hexColor.substr(5, 2), 16) / 255;
174 |
175 | this.polygon_.primitiveLine.material.uniforms['color'] =
176 | new Cesium.Color(r, g, b, opt_a);
177 |
178 | this.app.sceneChanged = true;
179 | };
180 |
181 |
182 | /**
183 | * @param {string} src URL of the image to use.
184 | * @param {number} width Desired width of the image in meters.
185 | * @param {number} height Desired height of the image in meters.
186 | */
187 | weapi.EditablePolygon.prototype.setIcon = function(src, width, height) {
188 | this.icon_.setImage(src, width, height);
189 | this.repositionIcon_();
190 | this.app.sceneChanged = true;
191 | };
192 |
193 |
194 | /**
195 | * @param {!function()} onchange Function to be called whenever polygon changes.
196 | */
197 | weapi.EditablePolygon.prototype.setOnChange = function(onchange) {
198 | this.onchange_ = onchange;
199 | };
200 |
201 |
202 | /**
203 | * @return {boolean} Is the polygon valid (non self-intersecting,...) ?
204 | */
205 | weapi.EditablePolygon.prototype.isValid = function() {
206 | return this.polygon_.isValid();
207 | };
208 |
209 |
210 | /**
211 | * @return {number} Rough area of the polygon in m^2.
212 | */
213 | weapi.EditablePolygon.prototype.getRoughArea = function() {
214 | return this.polygon_.getRoughArea();
215 | };
216 |
217 |
218 | /**
219 | * in degrees
220 | * @return {{lat: number, lng: number}|null}
221 | * Centroid of the polygon or null if not valid.
222 | */
223 | weapi.EditablePolygon.prototype.getCentroid = function() {
224 | var centroid = this.polygon_.calcCentroid();
225 | return {'lat': centroid[1], 'lng': centroid[0]};
226 | };
227 |
228 |
229 | /**
230 | * in degrees
231 | * @param {number} lat .
232 | * @param {number} lng .
233 | * @return {boolean} True if inside the polygon.
234 | */
235 | weapi.EditablePolygon.prototype.isPointIn = function(lat, lng) {
236 | //workaround: the mousedown/up events cause point adding,
237 | // but the click event can not be easily canceled so
238 | // it always causes polygon selection when click-to-adding
239 | if (goog.now() - this.lastClickToAdd_ < 100) return false;
240 | return this.polygon_.isPointIn(lat, lng);
241 | };
242 |
243 |
244 | /**
245 | * @param {!weapi.EditablePolygon} other .
246 | * @return {boolean} True if the two polygons overlap.
247 | */
248 | weapi.EditablePolygon.prototype.intersects = function(other) {
249 | return this.polygon_.intersects(other.polygon_);
250 | };
251 |
252 |
253 | /**
254 | * @private
255 | */
256 | weapi.EditablePolygon.prototype.repositionIcon_ = function() {
257 | var avg = this.polygon_.calcCentroid() || this.polygon_.calcAverage();
258 |
259 | this.icon_.setLatLng(goog.math.toRadians(avg[1]),
260 | goog.math.toRadians(avg[0]));
261 | this.icon_.enable(this.polygon_.isValid());
262 | };
263 |
264 |
265 | /**
266 | * @param {boolean} visible .
267 | * @param {boolean=} opt_midOnly .
268 | */
269 | weapi.EditablePolygon.prototype.showDraggers = function(visible, opt_midOnly) {
270 | goog.object.forEach(this.midMap_, function(el, key, obj) {
271 | el.enable(visible);
272 | }, this);
273 | if (opt_midOnly !== true) {
274 | goog.object.forEach(this.draggers_, function(el, key, obj) {
275 | this.markermanager_.getMarker(el).enable(visible);
276 | }, this);
277 | }
278 | this.app.sceneChanged = true;
279 | };
280 |
281 |
282 | /**
283 | * @return {!Array.} .
284 | */
285 | weapi.EditablePolygon.prototype.getPoints = function() {
286 | return this.polygon_.getAllCoords();
287 | };
288 |
289 |
290 | /**
291 | * Recalculates position of the two mid-edge draggers neighboring given point.
292 | * @param {number} fixedId .
293 | * @private
294 | */
295 | weapi.EditablePolygon.prototype.repositionMidsAround_ = function(fixedId) {
296 | var neighs = this.polygon_.getNeighbors(fixedId);
297 | if (neighs.length > 0) {
298 | var coordsPrev = this.polygon_.getCoords(neighs[0]);
299 | var coordsHere = this.polygon_.getCoords(fixedId);
300 | var coordsNext = this.polygon_.getCoords(neighs[1]);
301 | this.midMap_[fixedId].lat =
302 | goog.math.toRadians((coordsHere[1] + coordsNext[1]) / 2);
303 | this.midMap_[fixedId].lon =
304 | goog.math.toRadians((coordsHere[0] + coordsNext[0]) / 2);
305 | this.midMap_[neighs[0]].lat =
306 | goog.math.toRadians((coordsPrev[1] + coordsHere[1]) / 2);
307 | this.midMap_[neighs[0]].lon =
308 | goog.math.toRadians((coordsPrev[0] + coordsHere[0]) / 2);
309 | }
310 | this.app.sceneChanged = true;
311 | };
312 |
313 |
314 | /**
315 | * Checks, whether the polygon has just changed CW/CCW orientation
316 | * and performs necessary adjustments.
317 | * @private
318 | */
319 | weapi.EditablePolygon.prototype.checkPointOrientationChange_ = function() {
320 | if (this.polygon_.orientationChanged()) {
321 | goog.object.forEach(this.midMap_, function(el, key, obj) {
322 | this.repositionMidsAround_(key);
323 | }, this);
324 | }
325 | };
326 |
327 |
328 | /**
329 | * @param {!Array.} coords
330 | */
331 | weapi.EditablePolygon.prototype.addPoints = function(coords) {
332 | var l = coords.length;
333 | for (var i = 0; i < l - 1; i++) {
334 | this.addPoint(coords[i][0], coords[i][1], undefined, undefined, true);
335 | }
336 | this.addPoint(coords[i][0], coords[i][1]);
337 | };
338 |
339 |
340 | /**
341 | * @param {number} lat in degrees.
342 | * @param {number} lng in degrees.
343 | * @param {number=} opt_parent .
344 | * @param {boolean=} opt_fromMid .
345 | * @param {boolean=} opt_more More points coming?
346 | * @return {number} fixedId.
347 | */
348 | weapi.EditablePolygon.prototype.addPoint = function(lat, lng,
349 | opt_parent, opt_fromMid,
350 | opt_more) {
351 | var fixedId = this.polygon_.addPoint(lat, lng, opt_parent, opt_more);
352 |
353 | if (opt_fromMid && goog.isDefAndNotNull(opt_parent)) {
354 | this.draggers_[fixedId] = this.midDraggers_[opt_parent];
355 | delete this.midDraggers_[opt_parent];
356 | } else {
357 | var dragger = new weapi.markers.PolyDragger(
358 | goog.math.toRadians(lat), goog.math.toRadians(lng), this.app, fixedId,
359 | goog.bind(this.movePoint, this), goog.bind(this.removePoint, this));
360 | this.draggers_[fixedId] = this.markermanager_.addMarker(null, dragger);
361 | }
362 | this.repositionIcon_();
363 |
364 | var neighs = this.polygon_.getNeighbors(fixedId);
365 | if (neighs.length > 0) {
366 | var adderAfter = goog.bind(function(parentP) {
367 | return goog.bind(function(lat, lng) {
368 | return this.addPoint(lat, lng, parentP, true);
369 | }, this);
370 | }, this);
371 | var mid1 = new weapi.markers.PolyDragger(
372 | goog.math.toRadians(lat), goog.math.toRadians(lng), this.app, null,
373 | goog.bind(this.movePoint, this),
374 | goog.bind(this.removePoint, this),
375 | adderAfter(fixedId));
376 | this.midMap_[fixedId] = mid1;
377 | this.midDraggers_[fixedId] = this.markermanager_.addMarker(null, mid1);
378 |
379 | if (opt_fromMid) {
380 | var mid2 = new weapi.markers.PolyDragger(
381 | goog.math.toRadians(lat), goog.math.toRadians(lng), this.app, null,
382 | goog.bind(this.movePoint, this),
383 | goog.bind(this.removePoint, this),
384 | adderAfter(neighs[0]));
385 | this.midMap_[neighs[0]] = mid2;
386 | this.midDraggers_[neighs[0]] = this.markermanager_.addMarker(null, mid2);
387 | }
388 | this.repositionMidsAround_(neighs[0]);
389 | this.repositionMidsAround_(fixedId);
390 | this.repositionMidsAround_(neighs[1]);
391 | }
392 |
393 | this.checkPointOrientationChange_();
394 | this.onchange_();
395 |
396 | this.app.sceneChanged = true;
397 |
398 | return fixedId;
399 | };
400 |
401 |
402 | /**
403 | * @param {number} fixedId .
404 | * @param {number} lat in degrees.
405 | * @param {number} lng in degrees.
406 | */
407 | weapi.EditablePolygon.prototype.movePoint = function(fixedId, lat, lng) {
408 | this.polygon_.movePoint(fixedId, lat, lng);
409 | var marker = this.markermanager_.getMarker(this.draggers_[fixedId]);
410 | marker.lat = goog.math.toRadians(lat);
411 | marker.lon = goog.math.toRadians(lng);
412 | this.checkPointOrientationChange_();
413 | this.repositionMidsAround_(fixedId);
414 | this.repositionIcon_();
415 |
416 | this.app.sceneChanged = true;
417 |
418 | this.onchange_();
419 | };
420 |
421 |
422 | /**
423 | * @param {number} fixedId .
424 | */
425 | weapi.EditablePolygon.prototype.removePoint = function(fixedId) {
426 | var neighs = this.polygon_.getNeighbors(fixedId);
427 |
428 | this.polygon_.removePoint(fixedId);
429 |
430 | this.repositionIcon_();
431 | if (goog.isDefAndNotNull(this.draggers_[fixedId])) {
432 | this.markermanager_.removeMarker(this.draggers_[fixedId]);
433 | delete this.draggers_[fixedId];
434 |
435 | delete this.midMap_[fixedId];
436 | if (goog.isDefAndNotNull(this.midDraggers_[fixedId])) {
437 | this.markermanager_.removeMarker(this.midDraggers_[fixedId]);
438 | delete this.midDraggers_[fixedId];
439 | }
440 | }
441 |
442 | this.checkPointOrientationChange_();
443 |
444 | if (neighs.length > 0) {
445 | this.repositionMidsAround_(neighs[0]);
446 | this.repositionMidsAround_(fixedId);
447 | this.repositionMidsAround_(neighs[1]);
448 | }
449 |
450 | this.app.sceneChanged = true;
451 |
452 | this.onchange_();
453 | };
454 |
--------------------------------------------------------------------------------
/src/map.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.Map');
9 |
10 |
11 |
12 | /**
13 | * @param {!Cesium.ImageryLayer} layer .
14 | * @constructor
15 | */
16 | weapi.Map = function(layer) {
17 | /**
18 | * @type {!Cesium.ImageryLayer}
19 | */
20 | this.layer = layer;
21 |
22 | /**
23 | * @type {?weapi.App}
24 | */
25 | this.app = null;
26 | };
27 |
28 |
29 | /**
30 | * @param {number} minLat Minimal latitude in degrees.
31 | * @param {number} maxLat Maximal latitude in degrees.
32 | * @param {number} minLon Minimal longitude in degrees.
33 | * @param {number} maxLon Maximal longitude in degrees.
34 | */
35 | weapi.Map.prototype.setBoundingBox = function(minLat, maxLat,
36 | minLon, maxLon) {
37 | var extent = this.layer.imageryProvider['_rectangle'];
38 | extent.west = goog.math.toRadians(minLon);
39 | extent.south = goog.math.toRadians(goog.math.clamp(minLat, -85.051, 85.051));
40 | extent.east = goog.math.toRadians(maxLon);
41 | extent.north = goog.math.toRadians(goog.math.clamp(maxLat, -85.051, 85.051));
42 | if (this.app) this.app.sceneChanged = true;
43 | };
44 |
45 |
46 | /**
47 | * @param {number} opacity Opacity.
48 | */
49 | weapi.Map.prototype.setOpacity = function(opacity) {
50 | this.layer.alpha = opacity;
51 | if (this.app) this.app.sceneChanged = true;
52 | };
53 |
54 |
55 | /**
56 | * @return {number} Opacity.
57 | */
58 | weapi.Map.prototype.getOpacity = function() {
59 | return this.layer.alpha;
60 | };
61 |
--------------------------------------------------------------------------------
/src/maps.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.maps');
9 | goog.provide('weapi.maps.MapType');
10 |
11 | goog.require('goog.Uri.QueryData');
12 | goog.require('goog.structs.Map');
13 |
14 | goog.require('weapi.CustomMap');
15 | goog.require('weapi.Map');
16 |
17 |
18 | /**
19 | * Constants for map names.
20 | * @enum {string}
21 | */
22 | weapi.maps.MapType = {
23 | 'OSM': 'osm',
24 | 'BING': 'bing',
25 | 'WMS': 'wms',
26 | 'CUSTOM': 'custom'
27 | };
28 |
29 |
30 | /**
31 | * @type {!goog.structs.Map}
32 | */
33 | weapi.maps.mapMap = new goog.structs.Map();
34 |
35 |
36 | /**
37 | * TODO: cleanup
38 | * @param {?weapi.App} app .
39 | * @param {!weapi.maps.MapType} type Type of the map.
40 | * @param {!Object.|!Array.=} opt_opts Map options.
41 | * @return {weapi.Map} Initialized TileProvider.
42 | */
43 | weapi.maps.initMap = function(app, type, opt_opts) {
44 |
45 | /** @type {string} */
46 | var key = type;
47 |
48 | var mapopts = null;
49 | var aropts = null;
50 | if (goog.isDefAndNotNull(opt_opts)) {
51 | if (goog.isArray(opt_opts)) {
52 | if (opt_opts.length > 0) key += opt_opts[0];
53 | aropts = opt_opts;
54 | //alert(type + ' got array');
55 | } else {
56 | key += (opt_opts['name'] || opt_opts['url']);
57 | mapopts = opt_opts;
58 | //alert(type + ' got object');
59 | }
60 | }
61 |
62 | var secure = 'https:' == document.location.protocol;
63 | var protocol = (secure ? 'https:' : 'http:');
64 |
65 | var tileProvider;
66 |
67 | switch (type) {
68 | case weapi.maps.MapType.OSM:
69 | if (!mapopts) {
70 | mapopts = {};
71 | mapopts['url'] = protocol + '//a.tile.openstreetmap.org';
72 | }
73 | tileProvider = new Cesium.OpenStreetMapImageryProvider(mapopts);
74 | break;
75 | case weapi.maps.MapType.BING:
76 | if (aropts) {
77 | mapopts = {};
78 | mapopts['url'] = protocol + '//dev.virtualearth.net';
79 |
80 | if (aropts[0] == 'Aerial')
81 | mapopts['mapStyle'] = Cesium.BingMapsStyle.AERIAL;
82 | if (aropts[0] == 'AerialWithLabels')
83 | mapopts['mapStyle'] = Cesium.BingMapsStyle.AERIAL_WITH_LABELS;
84 | if (aropts[0] == 'Road')
85 | mapopts['mapStyle'] = Cesium.BingMapsStyle.ROAD;
86 |
87 | mapopts['key'] = aropts[1];
88 | }
89 | tileProvider = new Cesium.BingMapsImageryProvider(mapopts);
90 | break;
91 | case weapi.maps.MapType.WMS:
92 | //tileProvider = new Cesium.WebMapServiceImageryProvider(mapopts);
93 | if (aropts) {
94 | mapopts = {};
95 | mapopts['parameters'] = {};
96 | mapopts['url'] = aropts[1];
97 | if (aropts[2] && aropts[2].length > 0) {
98 | mapopts['parameters']['version'] = aropts[2];
99 | }
100 | mapopts['layers'] = aropts[3];
101 | mapopts['parameters']['crs'] = aropts[4];
102 | if (aropts[5] && aropts[5].length > 0) {
103 | mapopts['parameters']['format'] = aropts[5];
104 | }
105 | if (aropts[6] && aropts[6].length > 0) {
106 | mapopts['parameters']['styles'] = aropts[6];
107 | }
108 | if (aropts[7] && aropts[7].length > 0) {
109 | var q = new goog.Uri.QueryData(aropts[7]);
110 | goog.array.forEach(q.getKeys(), function(el, i, arr) {
111 | mapopts['parameters'][el] = q.get(el);
112 | });
113 | }
114 | if (aropts[4] == 'EPSG:900913' ||
115 | aropts[4] == 'EPSG:3857') {
116 | mapopts['tilingScheme'] = new Cesium.WebMercatorTilingScheme();
117 | }
118 | // ignore minzoom aropts[8];
119 | mapopts['maximumLevel'] = aropts[9];
120 | if (app) mapopts['proxy'] = app.mapProxyObject;
121 | }
122 | tileProvider = new Cesium.WebMapServiceImageryProvider(mapopts);
123 | break;
124 | case weapi.maps.MapType.CUSTOM:
125 | if (aropts) {
126 | mapopts = {};
127 | mapopts['url'] = aropts[1];
128 | mapopts['maximumLevel'] = aropts[3];
129 | mapopts['tileSize'] = aropts[4];
130 | mapopts['flipY'] = aropts[5];
131 | mapopts['subdomains'] = aropts[6];
132 | mapopts['copyright'] = aropts[7];
133 | if (app) mapopts['proxy'] = app.mapProxyObject;
134 | }
135 | tileProvider = new weapi.CustomMap(mapopts);
136 | break;
137 | default:
138 | alert('Unknown MapType \'' + type + '\' !');
139 | return null;
140 | break;
141 | }
142 |
143 | var map = new weapi.Map(new Cesium.ImageryLayer(
144 | /** @type {!Cesium.ImageryProvider} */(tileProvider)));
145 |
146 | if (mapopts && mapopts['opacity']) {
147 | map.setOpacity(parseFloat(mapopts['opacity']));
148 | }
149 | if (mapopts && mapopts['bounds']) {
150 | var b = mapopts['bounds'];
151 | map.setBoundingBox(b[0], b[1], b[2], b[3]);
152 | }
153 |
154 | weapi.maps.mapMap.set(key, map);
155 | if (app) app.listenCORSErrors(map.layer.imageryProvider['errorEvent']);
156 | return map;
157 | };
158 |
159 |
160 | /**
161 | * @param {!weapi.maps.MapType} type Type of the map.
162 | * @param {string=} opt_subtype Optional subtype of the map.
163 | * @return {weapi.Map} TileProvider.
164 | */
165 | weapi.maps.getMap = function(type, opt_subtype) {
166 | /** @type {string} */
167 | var key = type;
168 | if (goog.isDefAndNotNull(opt_subtype))
169 | key += opt_subtype;
170 |
171 | return /** @type {weapi.Map} */ (weapi.maps.mapMap.get(key));
172 | };
173 |
174 |
175 | /**
176 | * @param {!weapi.App} app .
177 | * Initializes maps that does not require any special parameters (keys etc.).
178 | */
179 | weapi.maps.initStatics = function(app) {
180 | weapi.maps.initMap(app, weapi.maps.MapType.OSM);
181 | };
182 |
--------------------------------------------------------------------------------
/src/markers/abstractmarker.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.markers.AbstractMarker');
9 |
10 | goog.require('goog.dom');
11 |
12 |
13 |
14 | /**
15 | * @param {number} lat Latitude to be displayed at.
16 | * @param {number} lon Longitude to be displayed at.
17 | * @param {!HTMLElement} element Element representing this marker.
18 | * @constructor
19 | */
20 | weapi.markers.AbstractMarker = function(lat, lon, element) {
21 | /**
22 | * @type {number}
23 | */
24 | this.lat = lat;
25 |
26 | /**
27 | * @type {number}
28 | */
29 | this.lon = lon;
30 |
31 | /**
32 | * @type {!HTMLElement}
33 | * @protected
34 | */
35 | this.element = element;
36 |
37 | /**
38 | * @type {Element}
39 | * @protected
40 | */
41 | this.parentElement = null;
42 |
43 | /**
44 | * @type {boolean}
45 | * @protected
46 | */
47 | this.enabled = true;
48 |
49 | /**
50 | * @type {boolean}
51 | * @protected
52 | */
53 | this.visible = false;
54 | };
55 |
56 |
57 | /**
58 | * Attaches this marker to given element.
59 | * Detaches if already attached somewhere else.
60 | * @param {!Element} parentElement Element to attach to.
61 | */
62 | weapi.markers.AbstractMarker.prototype.attach = function(parentElement) {
63 | if (this.parentElement) {
64 | this.detach();
65 | }
66 | this.parentElement = parentElement;
67 | this.show(true);
68 | };
69 |
70 |
71 | /**
72 | * Detaches this marker from it's parent
73 | */
74 | weapi.markers.AbstractMarker.prototype.detach = function() {
75 | if (this.parentElement) {
76 | this.show(false);
77 | this.parentElement = null;
78 | }
79 | };
80 |
81 |
82 | /**
83 | * Enables/disables the marker.
84 | * @param {boolean=} opt_enabled Whether this marker is enabled or not.
85 | * Default true.
86 | */
87 | weapi.markers.AbstractMarker.prototype.enable = function(opt_enabled) {
88 | this.enabled = opt_enabled || false;
89 | if (!this.enabled) this.show(false);
90 | };
91 |
92 |
93 | /**
94 | * @return {boolean} Whether this marker is enabled or not.
95 | */
96 | weapi.markers.AbstractMarker.prototype.isEnabled = function() {
97 | return this.enabled;
98 | };
99 |
100 |
101 | /**
102 | * @return {boolean} Whether this marker is enabled or not.
103 | */
104 | weapi.markers.AbstractMarker.prototype.isVisible = function() {
105 | return this.visible;
106 | };
107 |
108 |
109 | /**
110 | * Shows/hides the marker.
111 | * @param {boolean=} opt_visible Whether this marker is visible or not.
112 | * Default true.
113 | */
114 | weapi.markers.AbstractMarker.prototype.show = function(opt_visible) {
115 | var newVal = !(opt_visible === false);
116 | var change = !(this.visible == newVal);
117 | this.visible = newVal;
118 |
119 | if (change) {
120 | if (this.visible && this.parentElement) {
121 | goog.dom.appendChild(this.parentElement, this.element);
122 | } else {
123 | goog.dom.removeNode(this.element);
124 | }
125 | }
126 | };
127 |
128 |
129 | /**
130 | *
131 | * @param {number} x X.
132 | * @param {number} y Y.
133 | */
134 | weapi.markers.AbstractMarker.prototype.setXY = function(x, y) {
135 | this.element.style.left = x.toFixed() + 'px';
136 | this.element.style.top = y.toFixed() + 'px';
137 | if (this.enabled) this.show(true);
138 | };
139 |
140 |
141 | /**
142 | * Override this method, if you want your marker
143 | * to be renderable onto 2d canvas for screenshot purposes.
144 | * @param {!CanvasRenderingContext2D} ctx .
145 | */
146 | weapi.markers.AbstractMarker.prototype.draw2D = goog.nullFunction;
147 |
--------------------------------------------------------------------------------
/src/markers/basicmarker.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.markers.BasicMarker');
9 |
10 | goog.require('goog.dom');
11 |
12 | goog.require('weapi.markers.AbstractMarker');
13 |
14 |
15 |
16 | /**
17 | * @inheritDoc
18 | * @extends {we.ui.markers.AbstractMarker}
19 | * @constructor
20 | */
21 | weapi.markers.BasicMarker = function(lat, lon) {
22 |
23 | var image =
24 | 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3Zn' +
25 | 'PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjMy' +
26 | 'IiB3aWR0aD0iMTYiIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUo' +
27 | 'MCwtMTAyMC4zNjIyKSI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgZD0ibTE1' +
28 | 'LDcuODdjMCwzLjQ3LTIuNzcsNi4yOC02LjE5LDYuMjhzLTYuMTktMi44MS02LjE5LTYu' +
29 | 'MjgsMi43Ny02LjI4LDYuMTktNi4yOCw2LjE5LDIuODEsNi4xOSw2LjI4em0tNi4xNiwy' +
30 | 'My4ydi0xNi41IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHRyYW5zZm9ybT0idHJhbnNsYXRl' +
31 | 'KC0wLjcwNzEwNjc4LDEwMjAuNzE1OCkiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVj' +
32 | 'YXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjFweCIgZmlsbD0iI2UxMzUxZSIvPjwvZz48' +
33 | 'L3N2Zz4=';
34 |
35 | var elementStyle = 'position:absolute;width:16px;height:32px;' +
36 | 'background-image:url(data:image/svg+xml;base64,' + image +
37 | ');margin:-32px 0 0 -8px;opacity:0.8;';
38 |
39 | var el = goog.dom.createDom('div', {style: elementStyle});
40 |
41 | goog.base(this, lat, lon, /** @type {!HTMLElement} */ (el));
42 |
43 | this.show(false);
44 | };
45 | goog.inherits(weapi.markers.BasicMarker, weapi.markers.AbstractMarker);
46 |
47 |
--------------------------------------------------------------------------------
/src/markers/markermanager.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.markers.MarkerManager');
9 |
10 | goog.require('goog.structs.Map');
11 |
12 | goog.require('weapi.markers.AbstractMarker');
13 | goog.require('weapi.utils');
14 |
15 |
16 |
17 | /**
18 | * @param {!weapi.App} app .
19 | * @param {!Element} element Element where markers should be
20 | * created and positioned.
21 | * @constructor
22 | */
23 | weapi.markers.MarkerManager = function(app, element) {
24 | /**
25 | * @type {!weapi.App}
26 | * @private
27 | */
28 | this.app_ = app;
29 |
30 | /**
31 | * @type {!Element}
32 | * @private
33 | */
34 | this.element_ = element;
35 |
36 | /**
37 | * @type {!goog.structs.Map}
38 | * @private
39 | */
40 | this.markerMap_ = new goog.structs.Map();
41 | };
42 |
43 |
44 | /**
45 | * Adds marker.
46 | * @param {?string} key Key of the marker. If null, random string is generated.
47 | * @param {!weapi.markers.AbstractMarker} marker Marker to be added.
48 | * @return {string} Key that was actually used.
49 | */
50 | weapi.markers.MarkerManager.prototype.addMarker = function(key, marker) {
51 | var realKey = key || goog.string.getRandomString();
52 | marker.attach(this.element_);
53 | this.markerMap_.set(realKey, marker);
54 | return realKey;
55 | };
56 |
57 |
58 | /**
59 | * Returns marker with the given key.
60 | * @param {string} key Key of the marker.
61 | * @return {weapi.markers.AbstractMarker} Marker or undefined if key is
62 | * not present in the collection.
63 | */
64 | weapi.markers.MarkerManager.prototype.getMarker = function(key) {
65 | return /** @type {weapi.markers.AbstractMarker}*/ (this.markerMap_.get(key));
66 | };
67 |
68 |
69 | /**
70 | * Removes marker, does NOT dispose of it.
71 | * @param {string} key Key of the marker.
72 | * @return {weapi.markers.AbstractMarker} Marker that was removed or undefined
73 | * if key was not present.
74 | */
75 | weapi.markers.MarkerManager.prototype.removeMarker = function(key) {
76 | var marker = /** @type {weapi.markers.AbstractMarker}*/
77 | (this.markerMap_.get(key));
78 | if (goog.isDef(marker)) {
79 | marker.detach();
80 | this.markerMap_.remove(key);
81 | }
82 | return marker;
83 | };
84 |
85 |
86 | /**
87 | * Removes marker, does NOT dispose of it.
88 | * @param {weapi.markers.AbstractMarker} marker .
89 | */
90 | weapi.markers.MarkerManager.prototype.removeMarkerEx = function(marker) {
91 | goog.structs.forEach(this.markerMap_, function(val, key, col) {
92 | if (val == marker) this.removeMarker(key);
93 | }, this);
94 | };
95 |
96 |
97 | /**
98 | * Updates all markers it controls.
99 | */
100 | weapi.markers.MarkerManager.prototype.updateMarkers = function() {
101 | goog.array.forEach(this.markerMap_.getKeys(), this.updateMarker, this);
102 | };
103 |
104 |
105 | /**
106 | * Updates all markers it controls.
107 | * @param {string} key Key of marker to update.
108 | */
109 | weapi.markers.MarkerManager.prototype.updateMarker = function(key) {
110 | var marker = /** @type {weapi.markers.AbstractMarker}*/
111 | (this.markerMap_.get(key));
112 |
113 | if (marker.isEnabled()) {
114 | //window['console']['log'](marker.lat, marker.lon);
115 | var pos = weapi.utils.getXYForLatLng(this.app_,
116 | marker.lat, marker.lon);
117 | //window['console']['log'](pos);
118 | //window.title = pos;
119 | if (goog.isDefAndNotNull(pos)) {
120 | marker.setXY(pos[0], pos[1]);
121 | marker.show(pos[2] > 0);
122 | } else {
123 | marker.show(false);
124 | }
125 | }
126 | };
127 |
128 |
129 | /**
130 | * @param {function(!weapi.markers.AbstractMarker)} func Callback.
131 | */
132 | weapi.markers.MarkerManager.prototype.forEach = function(func) {
133 | goog.array.forEach(this.markerMap_.getKeys(), function(el, i, arr) {
134 | var marker = /** @type {weapi.markers.AbstractMarker}*/
135 | (this.markerMap_.get(el));
136 | if (marker) func(marker);
137 | }, this);
138 | };
139 |
--------------------------------------------------------------------------------
/src/markers/polydragger.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.markers.PolyDragger');
9 |
10 | goog.require('goog.dom');
11 | goog.require('goog.style');
12 |
13 | goog.require('weapi.markers.AbstractMarker');
14 | goog.require('weapi.utils');
15 |
16 |
17 |
18 | /**
19 | * @inheritDoc
20 | * @param {number} lat .
21 | * @param {number} lon .
22 | * @param {!weapi.App} app .
23 | * @param {?number} fixedId .
24 | * @param {!function(number, number, number)} updateFunc .
25 | * @param {!function(number)} deleteFunc .
26 | * @param {(function(number, number) : number)=} opt_createFunc .
27 | * @extends {weapi.markers.AbstractMarker}
28 | * @constructor
29 | */
30 | weapi.markers.PolyDragger = function(lat, lon, app, fixedId,
31 | updateFunc, deleteFunc, opt_createFunc) {
32 | var marker = goog.dom.createDom('div', {'class':
33 | 'we-polydragger-' + (goog.isDefAndNotNull(fixedId) ? 'a' : 'b')});
34 |
35 | goog.base(this, lat, lon, /** @type {!HTMLElement} */ (marker));
36 |
37 | this.show(false);
38 |
39 | goog.events.listen(marker, goog.events.EventType.MOUSEDOWN, function(e_) {
40 | if (e_.button == 0) {
41 | goog.events.listen(app.canvas,
42 | goog.events.EventType.MOUSEMOVE, function(e) {
43 | var carte = app.camera.camera.
44 | pickEllipsoid(new Cesium.Cartesian2(e.offsetX, e.offsetY));
45 | if (goog.isDefAndNotNull(carte)) {
46 | var carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(carte);
47 | this.lat = goog.math.toDegrees(carto.latitude);
48 | this.lon = goog.math.toDegrees(carto.longitude);
49 | this.setXY(e.offsetX, e.offsetY); //for smoother dragging
50 | if (goog.isDefAndNotNull(fixedId)) {
51 | updateFunc(fixedId, this.lat, this.lon);
52 | } else if (opt_createFunc) {
53 | fixedId = opt_createFunc(this.lat, this.lon);
54 | marker.className = 'we-polydragger-a';
55 | }
56 | e.preventDefault();
57 | }
58 | }, false, this);
59 | e_.preventDefault();
60 | }
61 | }, false, this);
62 |
63 | goog.events.listen(marker, goog.events.EventType.CLICK, function(e) {
64 | if (e.altKey && goog.isDefAndNotNull(fixedId)) {
65 | deleteFunc(fixedId);
66 | e.preventDefault();
67 | }
68 | }, false, this);
69 |
70 | goog.events.listen(marker, goog.events.EventType.MOUSEUP, function(e) {
71 | goog.events.removeAll(app.canvas,
72 | goog.events.EventType.MOUSEMOVE);
73 | }, false, this);
74 | };
75 | goog.inherits(weapi.markers.PolyDragger, weapi.markers.AbstractMarker);
76 |
77 |
78 | weapi.utils.installStyles(
79 | '.we-polydragger-a{position:absolute;width:8px;height:8px;z-index:100;' +
80 | 'margin-left:-4px;margin-top:-4px;background-color:#36f;' +
81 | 'cursor:pointer;border:1px solid blue;}'
82 | );
83 | weapi.utils.installStyles(
84 | '.we-polydragger-b{position:absolute;width:6px;height:6px;z-index:99;' +
85 | 'margin-left:-3px;margin-top:-3px;background-color:rgba(180,220,250,0.9);' +
86 | 'cursor:pointer;border:1px solid blue;}'
87 | );
88 |
--------------------------------------------------------------------------------
/src/markers/popup.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.markers.Popup');
9 |
10 | goog.require('goog.dom');
11 | goog.require('goog.style');
12 |
13 | goog.require('weapi.utils');
14 |
15 |
16 |
17 | /**
18 | * @param {string} contentHTML HTML content of the popup.
19 | * @param {number=} opt_maxWidth Maximal width of the popup (default = 300).
20 | * @param {boolean=} opt_closeButton Create close button? (default = true).
21 | * @constructor
22 | */
23 | weapi.markers.Popup = function(contentHTML, opt_maxWidth, opt_closeButton) {
24 |
25 |
26 | var content = goog.dom.createDom('div', {'class': 'we-pp-content'});
27 | content.innerHTML = contentHTML;
28 |
29 | var contentwrap = goog.dom.createDom('div',
30 | {'class': 'we-pp-wrapper'}, content);
31 |
32 | var tipcontainer = goog.dom.createDom(
33 | 'div', {'class': 'we-pp-tip-cont'},
34 | goog.dom.createDom('div', {'class': 'we-pp-tip'}));
35 |
36 | /**
37 | * @type {!HTMLElement}
38 | * @private
39 | */
40 | this.popup_ = /** @type {!HTMLElement} */
41 | (goog.dom.createDom('div', {'class': 'we-pp'}));
42 |
43 | if (opt_closeButton !== false) {
44 | var closebutton = goog.dom.createDom('a',
45 | {'class': 'we-pp-close', 'href': '#'});
46 | closebutton.onclick = goog.bind(this.show, this, false);
47 | goog.dom.appendChild(this.popup_, closebutton);
48 | }
49 | goog.dom.appendChild(this.popup_, contentwrap);
50 | goog.dom.appendChild(this.popup_, tipcontainer);
51 |
52 | var width = (opt_maxWidth || 300) + 2 * 20; // compensation for margin+border
53 | this.popup_.style.width = width.toFixed(0) + 'px';
54 | this.popup_.style.left = (-width / 2).toFixed(0) + 'px';
55 |
56 | this.show(false);
57 | };
58 |
59 |
60 | /**
61 | * @return {!HTMLElement} Element.
62 | */
63 | weapi.markers.Popup.prototype.getElement = function() {
64 | return this.popup_;
65 | };
66 |
67 |
68 | /**
69 | * Adjust some properties of this popup.
70 | * @param {number} markerHeight Height of the marker this popup is attached to.
71 | */
72 | weapi.markers.Popup.prototype.adjust = function(markerHeight) {
73 | this.popup_.style.bottom = markerHeight.toFixed(0) + 'px';
74 | };
75 |
76 |
77 | /**
78 | * Shows or hides the popup.
79 | * @param {boolean=} opt_visible Visible? If not given, toggle.
80 | */
81 | weapi.markers.Popup.prototype.show = function(opt_visible) {
82 | var visible = goog.isDefAndNotNull(opt_visible) ?
83 | opt_visible : parseFloat(this.popup_.style.opacity) !== 1;
84 | if (visible) {
85 | this.popup_.style.opacity = 1.0;
86 | this.popup_.style.visibility = 'visible';
87 | } else {
88 | this.popup_.style.opacity = 0.0;
89 |
90 | //this breaks the fade-out animation, but is important to fix dragging
91 | //TODO: timeout and then visibility:hidden ?
92 | this.popup_.style.visibility = 'hidden';
93 | }
94 | };
95 |
96 |
97 | weapi.utils.installStyles(
98 | '.we-pp-content p{margin:18px 0;text-align:justify;}' +
99 | '.we-pp-wrapper{padding:1px;text-align:left;border-radius:12px;}' +
100 | '.we-pp{z-index:100;-webkit-transition:opacity 0.2s linear;' +
101 | '-moz-transition:opacity 0.2s linear;-o-transition:opacity 0.2s linear;' +
102 | 'transition:opacity 0.2s linear;position:absolute;}' +
103 | '.we-pp-wrapper,.we-pp-tip{background:white;box-shadow:0 1px 10px #888;' +
104 | '-moz-box-shadow:0 1px 10px #888;-webkit-box-shadow:0 1px 14px #999;}' +
105 | '.we-pp-close{background-image:url(data:image/png;base64,iVBORw0KGgoAAAAN' +
106 | 'SUhEUgAAAAoAAAAKCAMAAAC67D+PAAAAk1BMVEX////Ny8vNy8vNy8vNy8vNy8vNy8vNy8vN' +
107 | 'y8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vN' +
108 | 'y8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vNy8vN' +
109 | 'y8vNy8vNy8vNy8vNy8sw0horAAAAMHRSTlMA/bGBFK1LgjellwUx2+VZo73SE6z5fQYOtzLR' +
110 | 'pxI22CC4g6ieV+yAEc8/ZocEHTzU+GNbAAAAV0lEQVQI1wXBBQKDMADAwLRl7szdgCks/3/d' +
111 | '7hh0ehl0W21IwZgNR44nTNVZribmC1WXqzVstmpIALu9Gg5HOJ1VNV64arjd1YKy8sEz+nrD' +
112 | '51tD0//xB/w6CnrIHetcAAAAAElFTkSuQmCC);position:absolute;top:9px;' +
113 | 'right:9px;width:10px;height:10px;overflow:hidden;}' +
114 | '.we-pp-content{display:inline-block;margin:13px 19px;' +
115 | 'font:12px/1.4 "Helvetica Neue",Arial,Helvetica,sans-serif;}' +
116 | '.we-pp-tip-cont{margin:0 auto;width:40px;height:16px;position:relative;' +
117 | 'overflow:hidden;}' +
118 | '.we-pp-tip{width:15px;height:15px;padding:1px;margin:-8px auto 0;' +
119 | '-moz-transform:rotate(45deg);-webkit-transform:rotate(45deg);' +
120 | '-o-transform:rotate(45deg);transform:rotate(45deg);}'
121 | );
122 |
--------------------------------------------------------------------------------
/src/markers/prettymarker.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.markers.PrettyMarker');
9 |
10 | goog.require('goog.dom');
11 | goog.require('goog.style');
12 |
13 | goog.require('weapi.markers.AbstractMarker');
14 | goog.require('weapi.markers.Popup');
15 | goog.require('weapi.utils');
16 |
17 |
18 |
19 | /**
20 | * @inheritDoc
21 | * @param {number} lat .
22 | * @param {number} lon .
23 | * @param {string=} opt_iconUrl URL of the icon to use instead of the default.
24 | * @param {number=} opt_width Width of the icon.
25 | * @param {number=} opt_height Height of the icon.
26 | * @extends {weapi.markers.AbstractMarker}
27 | * @constructor
28 | */
29 | weapi.markers.PrettyMarker = function(lat, lon,
30 | opt_iconUrl, opt_width, opt_height) {
31 |
32 | var marker = goog.dom.createDom('div', {'class': 'we-pm-icon'});
33 |
34 | if (goog.isString(opt_iconUrl))
35 | marker.style.backgroundImage = 'url(' + opt_iconUrl + ')';
36 |
37 | this.width_ = opt_width || 25;
38 | this.height_ = opt_height || 41;
39 |
40 | marker.style.width = this.width_.toFixed(0) + 'px';
41 | marker.style.height = this.height_.toFixed(0) + 'px';
42 | marker.style.marginLeft = (-this.width_ / 2).toFixed(0) + 'px';
43 | marker.style.marginTop = (-this.height_).toFixed(0) + 'px';
44 |
45 | //wrapper for marker and popup
46 | var elwrap = goog.dom.createDom('div', {style: 'position:absolute;'},
47 | marker);
48 |
49 | goog.base(this, lat, lon, /** @type {!HTMLElement} */ (elwrap));
50 |
51 | /**
52 | * @type {weapi.markers.Popup}
53 | * @private
54 | */
55 | this.popup_ = null;
56 |
57 | this.show(false);
58 | this.showPopup(false);
59 |
60 | marker.onclick = goog.bind(this.showPopup, this, undefined);
61 |
62 | };
63 | goog.inherits(weapi.markers.PrettyMarker, weapi.markers.AbstractMarker);
64 |
65 |
66 | /**
67 | * Bind the popup to this marker.
68 | * @param {!weapi.markers.Popup} popup Popup to bind.
69 | */
70 | weapi.markers.PrettyMarker.prototype.attachPopup = function(popup) {
71 | if (this.popup_) goog.dom.removeNode(this.popup_.getElement());
72 |
73 | this.popup_ = popup;
74 | goog.dom.appendChild(this.element, this.popup_.getElement());
75 | popup.adjust(this.height_);
76 | };
77 |
78 |
79 | /**
80 | * Shows or hides the popup.
81 | * @param {boolean=} opt_visible Visible? If not given, toggle.
82 | */
83 | weapi.markers.PrettyMarker.prototype.showPopup = function(opt_visible) {
84 | if (this.popup_) {
85 | //var popup = this.popup_.getElement();
86 | //center the popup
87 | //popup.style.left = -Math.round((popup.offsetWidth -
88 | // this.element.offsetWidth) / 2) + 'px';
89 |
90 | this.popup_.show(opt_visible);
91 | }
92 | };
93 |
94 |
95 | weapi.utils.installStyles(
96 | '.we-pm-icon{position:absolute;z-index:64;background-image:url(data:image' +
97 | '/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFtklEQVR42p3W' +
98 | 'a0xTZxgH8CYkS5aQLGFxcXNZNNu4FIFCK9eCWOQm3lDizLYP+zTnxLk5wQpICxRaLuWi3FpQ' +
99 | 'FMU55xDkjiBTFMeA9Zy2JzMxWUZisoS4TyRuQvG/pydQKb3Q2uSXw3nf93n+5z2nLRVIiga8' +
100 | '8RERL/MlAgDrosJ+d/zISTJMEFt2B9LyEV5kySBo7G+iI9vdhyj7nfEhcjKfUvcAma0z+Pwa' +
101 | '5+BwuxG7myaRUH4XtHaAhLgI6VvLjwwn14zj0CUWn13lkHXZhPQWBml6e3svGq3zvIyGX0F1' +
102 | 'z0mWQ4hY0buaH3mcUvsAn17hcKDNhFQdsy4K5Nfv108jsXIM1OOoqxAfMppMAYepIF3PIrmZ' +
103 | '8cqhy2ZkXjBYQyxE9iqksGeFXKa9h0PtHF0di6Qmxhk+fKeLOcLX72r8DdRvjvjxIRGFt638' +
104 | 'yPz+Vha7W0zY0cDYydAbkXWJ4+1rYWx/H2gzO6wl/JysehzUUw0+5Oxtq+zEyns42MYhsZ6x' +
105 | 'Q2N0C0yQae9DouhFZt1dOvYgnt5RGU3T/Dw1tqtJ1xn5Gur7D/ERRBR0W43vapzhd5FwnrHZ' +
106 | '02Kmt+jvkBTeRsOdPzD/7wJWXkPGp0hSD2BnzUMcuMjZ1RF+LFY1BOotFYQXdPkSy/5WDvF1' +
107 | 'DKSr8GOau7g1/RecvWafzSOuuBf79CYkNxodandox0G9VYLw/FvCSGUf9rZQci1js6PeiPRG' +
108 | 'Aw7WjTgNePnyJa9mgJ5h1QPsWVOf0kTBdZOg/p0CUV6nNKp4ALv1HKJrGJu0Znqo1RNQds64' +
109 | 'DLBYLBjjniK6ZMihXtZgRPK5KVD/UYHozM+R1p1k6DhEaRmblAYK0U4g/8aUy4DFxUUMMLOI' +
110 | 'UQ071CfVm5BUOwnq30shNzeJC7qwq5nDtirGJq6GRWoDixR1HxYsS3YhS0tLWFhY4OVdf4TE' +
111 | 'qgmkNprt6ukiafwhqL9OECb/yYfMpzaaEKllIa5kbNKa6D6XjqK632i3C9oBH3DPPIttyn5+' +
112 | 'XUy1Y22cegzUO18QdvqGVWdC5SNK5xBezqyg3dB9pW1Hl4wgu20cM3/O4b+FRTx++gwNgywk' +
113 | 'eTfhrE5cwfBj4oJuUG+hIDT3RyuZpLAXO+s5hGkYOzHVRn5cWv4Q4rM91iJE5HchpnQMsjrW' +
114 | 'WY1tPfUd5j/xoTnXV0zEV05Cds6MUDVjR6RhkXSecxBTbXJYG0ZojnbRA+opXQ75YUWmpLCf' +
115 | 'ro7D1lLmdfH1Us0EqN+o7Vs45NS1FT7kSXzVDGK1ZghVzGtJpJAI2gX1Sn4V8n3HakcliiEk' +
116 | '1HIILGG8FldtRqxmEtRn2u6f1taTHau9SebitCzCNUb4FzNekdZwCC/oA/XIsg/57upaKrFi' +
117 | 'BHFaDh8rGY9FVZgRrZ4C1T8mPnYhwd9eWWsDeR5TaUKwyogPFYxHYqo4iPL7QbVf8M3tQ9qd' +
118 | '0UUoxhBdyWFLIbOucLUJkeoZUN0secMx5MRlZwKIhW4BXSWLzWcZt6IqrLsYBNWccPq7S/jN' +
119 | 'JVe6wxX3sU1jxgf5BpeExUZIygyg9XPE13nI8TZXtm89dRNiCnk/z+AKPx+aNwhaX+jyZ2pQ' +
120 | '9kV3JkTKRxCVmrDpjMGBv4JFRBkLWjdP3nITcsGdT4JzuiAqM+NduWEtfjzkzBBoXQXf0GXI' +
121 | 'sVZ3fMiTsKIpBBYZsfG0wWZzAQuRygSaf0E2ug0J/LplPdnBOT0IVZnxTq5hBX8eLB8Gzev4' +
122 | 'Zu5D9OvxJXMhRQZsoavfkEPPQ84gpMQEGreQLeuGBBzVe0IVlDuA4GIz3j5l4I9B8hHQeIfb' +
123 | 'AFvIVzpPbCQvhEoT3pOzEBaZEZh9BTQW4mFIs6d0gbl3EKSkAPkY6LyXCDwLOdLsKWHAsXYE' +
124 | 'KswIOH4NdC4lnoX4H2nyRnfA6THQ8Zflcw9Dvmz0xnb/7A7QMW353OMQb7USgTch/wNGSWfR' +
125 | 'l/HE1wAAAABJRU5ErkJggg==);}'
126 | );
127 |
--------------------------------------------------------------------------------
/src/miniglobe.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.MiniGlobe');
9 |
10 | goog.require('goog.dom');
11 |
12 |
13 |
14 | /**
15 | * @param {!weapi.App} app .
16 | * @param {number} latBands .
17 | * @param {number} lngBands .
18 | * @param {string} textureUrl .
19 | * @constructor
20 | */
21 | weapi.MiniGlobe = function(app, latBands, lngBands, textureUrl) {
22 | /**
23 | * @type {!weapi.App}
24 | * @private
25 | */
26 | this.app_ = app;
27 |
28 | /**
29 | * @type {!HTMLCanvasElement}
30 | */
31 | this.canvas =
32 | /** @type {!HTMLCanvasElement} */(goog.dom.createElement('canvas'));
33 |
34 | var par = this.app_.canvas.parentElement || window.document;
35 | goog.dom.append(par, this.canvas);
36 |
37 | var opts = {'depth': false, 'preserveDrawingBuffer': true};
38 |
39 | /**
40 | * @type {?WebGLRenderingContext}
41 | */
42 | this.gl = /** @type {?WebGLRenderingContext} */
43 | (this.canvas.getContext('webgl', opts) ||
44 | this.canvas.getContext('experimental-webgl', opts));
45 |
46 | var gl = this.gl;
47 |
48 | gl.enable(gl.CULL_FACE);
49 | gl.cullFace(gl.BACK);
50 | gl.clearColor(0.0, 0.0, 0.0, 0.0);
51 |
52 | this.vertexBuffer_ = gl.createBuffer();
53 |
54 | var radius = 1;
55 | var vertexPositionData = [], textureCoordData = [], indexData = [];
56 | for (var latNumber = 0; latNumber <= latBands; latNumber++) {
57 | var theta = latNumber * Math.PI / latBands;
58 | var sinTheta = Math.sin(theta);
59 | var cosTheta = Math.cos(theta);
60 |
61 | for (var longNumber = 0; longNumber <= lngBands; longNumber++) {
62 | var phi = longNumber * 2 * Math.PI / lngBands;
63 | phi -= Math.PI / 2;
64 | var sinPhi = Math.sin(phi);
65 | var cosPhi = Math.cos(phi);
66 |
67 | var x = cosPhi * sinTheta;
68 | var y = cosTheta;
69 | var z = sinPhi * sinTheta;
70 | var u = 1 - (longNumber / lngBands);
71 | var v = 1 - (latNumber / latBands);
72 |
73 | textureCoordData.push(u);
74 | textureCoordData.push(v);
75 | vertexPositionData.push(radius * x);
76 | vertexPositionData.push(radius * y);
77 | vertexPositionData.push(radius * z);
78 | }
79 | }
80 |
81 | for (var latNumber = 0; latNumber < latBands; latNumber++) {
82 | for (var longNumber = 0; longNumber < lngBands; longNumber++) {
83 | var first = (latNumber * (lngBands + 1)) + longNumber;
84 | var second = first + lngBands + 1;
85 | indexData.push(first + 1);
86 | indexData.push(second);
87 | indexData.push(first);
88 |
89 | indexData.push(first + 1);
90 | indexData.push(second + 1);
91 | indexData.push(second);
92 | }
93 | }
94 |
95 | this.vertexBuffer_ = gl.createBuffer();
96 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer_);
97 | gl.bufferData(gl.ARRAY_BUFFER,
98 | new Float32Array(vertexPositionData), gl.STATIC_DRAW);
99 | this.vertexBuffer_.itemSize = 3;
100 | this.vertexBuffer_.numItems = vertexPositionData.length / 3;
101 |
102 | this.texCoordBuffer_ = gl.createBuffer();
103 | gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer_);
104 | gl.bufferData(gl.ARRAY_BUFFER,
105 | new Float32Array(textureCoordData), gl.STATIC_DRAW);
106 | this.texCoordBuffer_.itemSize = 2;
107 | this.texCoordBuffer_.numItems = textureCoordData.length / 2;
108 |
109 | this.indexBuffer_ = gl.createBuffer();
110 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer_);
111 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,
112 | new Uint16Array(indexData), gl.STATIC_DRAW);
113 | this.indexBuffer_.itemSize = 1;
114 | this.indexBuffer_.numItems = indexData.length;
115 |
116 | /**
117 | * @type {?WebGLTexture}
118 | * @private
119 | */
120 | this.texture_ = null;
121 |
122 | var image_ = new Image();
123 | image_.onload = goog.bind(function() {
124 | this.texture_ = gl.createTexture();
125 | gl.activeTexture(gl.TEXTURE0);
126 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
127 | gl.bindTexture(gl.TEXTURE_2D, this.texture_);
128 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image_);
129 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
130 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
131 | }, this);
132 | image_.src = textureUrl;
133 |
134 | var fsCode = 'precision lowp float;' +
135 | 'varying vec2 vTextureCoord;' +
136 | 'uniform sampler2D uSampler;' +
137 | 'void main(){gl_FragColor=texture2D(uSampler,vTextureCoord);}';
138 | var vsCode = 'precision mediump float;' +
139 | 'attribute vec3 aVertexPosition;' +
140 | 'attribute vec2 aTextureCoord;' +
141 | 'uniform mat4 uMVMatrix;' +
142 | 'uniform mat4 uPMatrix;' +
143 | 'uniform float uAspect;' +
144 | 'varying vec2 vTextureCoord;' +
145 | 'void main(){' +
146 | 'gl_Position=uPMatrix*uMVMatrix*vec4(aVertexPosition,1.0);' +
147 | 'gl_Position.x*=uAspect;' +
148 | 'gl_Position.z=0.0;' +
149 | 'vTextureCoord=aTextureCoord;' +
150 | '}';
151 |
152 | var createShader = function(shaderCode, shaderType) {
153 | var shader = gl.createShader(shaderType);
154 |
155 | gl.shaderSource(shader, shaderCode);
156 | gl.compileShader(shader);
157 |
158 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
159 | throw Error('Shader err: ' + gl.getShaderInfoLog(shader));
160 | } else if (goog.isNull(shader)) {
161 | throw Error('Unknown');
162 | }
163 |
164 | return shader;
165 | };
166 |
167 | var fsshader = createShader(fsCode, gl.FRAGMENT_SHADER);
168 | var vsshader = createShader(vsCode, gl.VERTEX_SHADER);
169 |
170 | this.program_ = gl.createProgram();
171 | if (goog.isNull(this.program_)) {
172 | throw Error('Unknown');
173 | }
174 | gl.attachShader(this.program_, vsshader);
175 | gl.attachShader(this.program_, fsshader);
176 |
177 | gl.bindAttribLocation(this.program_, 0, 'aVertexPosition');
178 |
179 | gl.linkProgram(this.program_);
180 |
181 | if (!gl.getProgramParameter(this.program_, gl.LINK_STATUS)) {
182 | throw Error('Shader program err: ' +
183 | gl.getProgramInfoLog(this.program_));
184 | }
185 |
186 | gl.useProgram(this.program_);
187 |
188 | this.vertexPositionAttribute =
189 | gl.getAttribLocation(this.program_, 'aVertexPosition');
190 | this.textureCoordAttribute =
191 | gl.getAttribLocation(this.program_, 'aTextureCoord');
192 |
193 | this.aspectUniform = gl.getUniformLocation(this.program_, 'uAspect');
194 | this.pMatrixUniform = gl.getUniformLocation(this.program_, 'uPMatrix');
195 | this.mvMatrixUniform = gl.getUniformLocation(this.program_, 'uMVMatrix');
196 | this.samplerUniform = gl.getUniformLocation(this.program_, 'uSampler');
197 |
198 | /**
199 | * @type {number}
200 | * @private
201 | */
202 | this.size_ = 128;
203 |
204 | /**
205 | * @type {number}
206 | * @private
207 | */
208 | this.padding_ = 0.1;
209 | };
210 |
211 |
212 | /**
213 | * @param {number} size Size of the mini globe in pixels.
214 | * @param {number=} opt_padding Relative padding (0.1 ~= 10% padding).
215 | */
216 | weapi.MiniGlobe.prototype.setSize = function(size, opt_padding) {
217 | this.size_ = size;
218 | if (goog.isDefAndNotNull(opt_padding)) this.padding_ = opt_padding;
219 | this.canvas.width = this.size_;
220 | this.canvas.height = this.size_;
221 | this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
222 |
223 | var pad = this.padding_ * this.size_;
224 | this.canvas.style.cssText = 'position:absolute;z-index:10000;' +
225 | 'pointer-events:none;' +
226 | 'right:' + pad + 'px;bottom:' + pad + 'px;';
227 | };
228 |
229 |
230 | /**
231 | * @param {!CanvasRenderingContext2D} dst .
232 | */
233 | weapi.MiniGlobe.prototype.drawToCanvas2D = function(dst) {
234 | var cornerOff = this.size_ * (1 + this.padding_);
235 | var x = this.app_.canvas.width - cornerOff;
236 | var y = this.app_.canvas.height - cornerOff;
237 | dst.drawImage(this.canvas, x, y);
238 | };
239 |
240 |
241 | /**
242 | * Draw
243 | */
244 | weapi.MiniGlobe.prototype.draw = function() {
245 | if (!goog.isDefAndNotNull(this.texture_)) return;
246 | var gl = this.gl;
247 | gl.useProgram(this.program_);
248 |
249 | gl.activeTexture(gl.TEXTURE0);
250 | gl.bindTexture(gl.TEXTURE_2D, this.texture_);
251 | gl.uniform1i(this.samplerUniform, 0);
252 |
253 | var rotate100 = function(what, angle) {
254 | var c = Math.cos(angle), s = Math.sin(angle);
255 |
256 | Cesium.Matrix4.multiply(what, new Cesium.Matrix4(
257 | 1, 0, 0, 0,
258 | 0, c, -s, 0,
259 | 0, s, c, 0,
260 | 0, 0, 0, 1
261 | ), what);
262 | };
263 |
264 | var rotate010 = function(what, angle) {
265 | var c = Math.cos(angle), s = Math.sin(angle);
266 |
267 | Cesium.Matrix4.multiply(what, new Cesium.Matrix4(
268 | c, 0, s, 0,
269 | 0, 1, 0, 0,
270 | -s, 0, c, 0,
271 | 0, 0, 0, 1
272 | ), what);
273 | };
274 |
275 | var pos = this.app_.camera.getPos();
276 | var frustum = this.app_.camera.camera.frustum;
277 |
278 | var mvm = Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(
279 | 0, 0, -1.3 / Math.tan(frustum.fovy / 2)));
280 | rotate100(mvm, pos[0]);
281 | rotate010(mvm, -pos[1]);
282 |
283 |
284 | var pm = new Float32Array(goog.array.flatten(
285 | Cesium.Matrix4.toArray(frustum.projectionMatrix)));
286 |
287 | gl.uniformMatrix4fv(this.mvMatrixUniform, false, new Float32Array(
288 | goog.array.flatten(Cesium.Matrix4.toArray(mvm))));
289 | gl.uniformMatrix4fv(this.pMatrixUniform, false, pm);
290 | gl.uniform1f(this.aspectUniform, frustum.aspectRatio);
291 |
292 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer_);
293 | gl.vertexAttribPointer(this.vertexPositionAttribute,
294 | this.vertexBuffer_.itemSize, gl.FLOAT, false, 0, 0);
295 |
296 | gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer_);
297 | gl.vertexAttribPointer(this.textureCoordAttribute,
298 | this.texCoordBuffer_.itemSize, gl.FLOAT, false, 0, 0);
299 |
300 | gl.enableVertexAttribArray(this.vertexPositionAttribute);
301 | gl.enableVertexAttribArray(this.textureCoordAttribute);
302 |
303 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer_);
304 | gl.drawElements(gl.TRIANGLES, this.indexBuffer_.numItems,
305 | gl.UNSIGNED_SHORT, 0);
306 |
307 | gl.disableVertexAttribArray(this.vertexPositionAttribute);
308 | gl.disableVertexAttribArray(this.textureCoordAttribute);
309 | };
310 |
--------------------------------------------------------------------------------
/src/polygon.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WebGL Earth API - Polygon Example
8 |
9 |
10 |
11 |
12 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | Change fill color:
155 |
156 | #7bfffb
157 | #f8ff7d
158 | #5aff5a
159 | #fc7361
160 | #ee84fc
161 | #bab3b8
162 |
163 | Change border color:
164 |
165 | #7ad3ff
166 | #ffdb74
167 | #fc2009
168 | #fc7361
169 | #f225fc
170 | #7b7679
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
--------------------------------------------------------------------------------
/src/polygon.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @fileoverview Object representing single polygon + useful operations.
4 | * The polygon can even be concave, the class calculates
5 | * a triangulation of the polygon to be able to render it.
6 | *
7 | * @author petr.sloup@klokantech.com (Petr Sloup)
8 | *
9 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
10 | */
11 |
12 | goog.provide('weapi.Polygon');
13 |
14 |
15 |
16 | /**
17 | * @constructor
18 | */
19 | weapi.Polygon = function() {
20 | /**
21 | * @type {!Cesium.Polygon}
22 | */
23 | this.primitive = new Cesium.Polygon({'asynchronous': false});
24 |
25 | /**
26 | * @type {!Cesium.PolylineCollection}
27 | */
28 | this.primitiveLineCol = new Cesium.PolylineCollection();
29 |
30 | /**
31 | * @type {!Cesium.Polyline}
32 | */
33 | this.primitiveLine = this.primitiveLineCol.add();
34 |
35 | /**
36 | * @type {?weapi.Polygon.Node}
37 | * @private
38 | */
39 | this.head_ = null;
40 |
41 | /**
42 | * @type {!Array.}
43 | * @private
44 | */
45 | this.vertices_ = [];
46 |
47 | /**
48 | * @type {number}
49 | * @private
50 | */
51 | this.numVertices_ = 0;
52 |
53 | this.primitive.material.uniforms['color'] = new Cesium.Color(1, 0, 0, .8);
54 |
55 | this.primitiveLine.material.uniforms['color'] =
56 | new Cesium.Color(0, 0, 0, 1);
57 | this.primitiveLine.width = 2;
58 |
59 | /**
60 | * @type {number}
61 | * @private
62 | */
63 | this.roughArea_ = 0;
64 |
65 | /**
66 | * @type {boolean}
67 | * @private
68 | */
69 | this.valid_ = false;
70 |
71 | /**
72 | * @type {boolean}
73 | * @private
74 | */
75 | this.pointSwitchFlag_ = false;
76 |
77 | /**
78 | * @type {!Array.>}
79 | * @private
80 | */
81 | this.triangulation_ = [];
82 | };
83 |
84 |
85 | /**
86 | * @return {boolean} True if the polygon is valid (non self-intersecting).
87 | */
88 | weapi.Polygon.prototype.isValid = function() {
89 | return this.valid_;
90 | };
91 |
92 |
93 | /**
94 | * @return {boolean} True if the polygon CCW/CW orientation was just changed.
95 | */
96 | weapi.Polygon.prototype.orientationChanged = function() {
97 | var oldVal = this.pointSwitchFlag_;
98 | this.pointSwitchFlag_ = false;
99 | return oldVal;
100 | };
101 |
102 |
103 | /**
104 | * @return {number} Rough area of the polygon in m^2.
105 | */
106 | weapi.Polygon.prototype.getRoughArea = function() {
107 | return this.roughArea_;
108 | };
109 |
110 |
111 | /**
112 | * @param {number} lat in degrees.
113 | * @param {number} lng in degrees.
114 | * @param {number=} opt_parent Defaults to the last point.
115 | * @param {boolean=} opt_more More points coming?
116 | * @return {number} Fixed ID of the new point.
117 | */
118 | weapi.Polygon.prototype.addPoint = function(lat, lng, opt_parent, opt_more) {
119 | var vert = new weapi.Polygon.Node(lat, lng);
120 |
121 | if (this.numVertices_ == 0) {
122 | this.head_ = vert;
123 | vert.next = vert;
124 | vert.prev = vert;
125 | } else {
126 | var parent = this.vertices_[
127 | goog.math.clamp(goog.isDefAndNotNull(opt_parent) ?
128 | opt_parent : Number.MAX_VALUE,
129 | 0, this.vertices_.length - 1)];
130 | if (!parent) {
131 | parent = this.head_.prev;
132 | }
133 | vert.next = parent.next;
134 | parent.next = vert;
135 | vert.prev = parent;
136 | vert.next.prev = vert;
137 | }
138 | this.vertices_.push(vert);
139 | vert.fixedId = this.vertices_.length - 1;
140 | this.numVertices_++;
141 |
142 | if (opt_more !== true) {
143 | this.rebufferPoints_();
144 | this.solveTriangles_();
145 | }
146 |
147 | return vert.fixedId;
148 | };
149 |
150 |
151 | /**
152 | * @param {number} fixedId .
153 | * @return {!Array.} .
154 | */
155 | weapi.Polygon.prototype.getNeighbors = function(fixedId) {
156 | var vert = this.vertices_[fixedId];
157 | if (!vert) return [];
158 |
159 | return [vert.prev.fixedId, vert.next.fixedId];
160 | };
161 |
162 |
163 | /**
164 | * in degrees
165 | * @param {number} fixedId .
166 | * @return {!Array.} .
167 | */
168 | weapi.Polygon.prototype.getCoords = function(fixedId) {
169 | var vert = this.vertices_[fixedId];
170 | if (!vert) return [];
171 |
172 | var mod = 180 / Math.PI;
173 | return [vert.x * mod, vert.y * mod];
174 | };
175 |
176 |
177 | /**
178 | * in degrees
179 | * @return {!Array.} .
180 | */
181 | weapi.Polygon.prototype.getAllCoords = function() {
182 | var mod = 180 / Math.PI;
183 | var result = [];
184 |
185 | var vrt = this.head_;
186 | do {
187 | result.push({'lat': vrt.y * mod, 'lng': vrt.x * mod});
188 | vrt = vrt.next;
189 | } while (vrt != this.head_);
190 |
191 | return result;
192 | };
193 |
194 |
195 | /**
196 | * @param {number} fixedId .
197 | * @param {number} lat in degrees.
198 | * @param {number} lng in degrees.
199 | */
200 | weapi.Polygon.prototype.movePoint = function(fixedId, lat, lng) {
201 | var vert = this.vertices_[fixedId];
202 | if (!vert) return;
203 |
204 | vert.setLatLng(lat, lng);
205 |
206 | this.rebufferPoints_();
207 | this.solveTriangles_();
208 | };
209 |
210 |
211 | /**
212 | * @param {number} fixedId .
213 | */
214 | weapi.Polygon.prototype.removePoint = function(fixedId) {
215 | var vert = this.vertices_[fixedId];
216 | if (vert) {
217 | if (this.head_ == vert) {
218 | this.head_ = (vert == vert.next) ? null : vert.next;
219 | }
220 | vert.next.prev = vert.prev;
221 | vert.prev.next = vert.next;
222 | delete this.vertices_[fixedId];
223 | this.numVertices_--;
224 |
225 | this.rebufferPoints_();
226 | this.solveTriangles_();
227 | }
228 | };
229 |
230 |
231 | /**
232 | * @return {!Array.} Coords of the average of the nodes.
233 | */
234 | weapi.Polygon.prototype.calcAverage = function() {
235 | if (!this.head_) return [0, 0];
236 | var x = 0, y = 0, i = 0;
237 | var vrt = this.head_;
238 | do {
239 | x += vrt.x;
240 | y += vrt.y;
241 | i++;
242 | vrt = vrt.next;
243 | } while (vrt != this.head_);
244 | i = i / 180 * Math.PI;
245 | return [x/i, y/i];
246 | };
247 |
248 |
249 | /**
250 | * @return {Array.} Centroid of the polygon or null if not valid.
251 | */
252 | weapi.Polygon.prototype.calcCentroid = function() {
253 | if (!this.isValid()) return null;
254 |
255 | var x = 0, y = 0, area = 0;
256 | var vrt = this.head_;
257 | do {
258 | var p1 = vrt, p2 = vrt.next;
259 | var f = p1.x * p2.y - p2.x * p1.y;
260 | x += (p1.x + p2.x) * f;
261 | y += (p1.y + p2.y) * f;
262 | area += f;
263 |
264 | vrt = vrt.next;
265 | } while (vrt != this.head_);
266 | area /= 2;
267 |
268 | var f = (6 * area) / (180 / Math.PI);
269 | return [x / f, y / f];
270 | };
271 |
272 |
273 | /**
274 | * @param {number} lat in degrees.
275 | * @param {number} lng in degrees.
276 | * @return {boolean} True if inside the polygon.
277 | */
278 | weapi.Polygon.prototype.isPointIn = function(lat, lng) {
279 | var p1x = lng / 180 * Math.PI;
280 | var p1y = lat / 180 * Math.PI;
281 |
282 | var sign_ = function(p2, p3) {
283 | return (p1x - p3.x) * (p2.y - p3.y) -
284 | (p2.x - p3.x) * (p1y - p3.y);
285 | };
286 | var found = false;
287 | goog.array.forEach(this.triangulation_, function(el, i, arr) {
288 | var b1 = sign_(el[0], el[1]) < 0;
289 | var b2 = sign_(el[1], el[2]) < 0;
290 | var b3 = sign_(el[2], el[0]) < 0;
291 |
292 | if ((b1 == b2) && (b2 == b3)) found = true;
293 | });
294 | return found;
295 | };
296 |
297 |
298 | /**
299 | * Test polygon<->polygon intersection based on the triangulations.
300 | * TODO: Could be optimized, but seems to be performing very well.
301 | * @param {!weapi.Polygon} other .
302 | * @return {boolean} True if the two polygons overlap.
303 | */
304 | weapi.Polygon.prototype.intersects = function(other) {
305 | var lineInter = function(a, b, c, d) {
306 | //test ab<->cd intersection
307 | var denom = (d.y - c.y) * (b.x - a.x) - (d.x - c.x) * (b.y - a.y);
308 | var aycy = (a.y - c.y), axcx = (a.x - c.x);
309 | var p = ((d.x - c.x) * aycy - (d.y - c.y) * axcx) / denom;
310 | var t = ((b.x - a.x) * aycy - (b.y - a.y) * axcx) / denom;
311 |
312 | return (p > 0 && p < 1 && t > 0 && t < 1);
313 | };
314 | var isPointIn = function(p, t) {
315 | var sign_ = function(p2, p3) {
316 | return (p.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p.y - p3.y);
317 | };
318 |
319 | var b1 = sign_(t[0], t[1]) < 0;
320 | var b2 = sign_(t[1], t[2]) < 0;
321 | var b3 = sign_(t[2], t[0]) < 0;
322 |
323 | return (b1 == b2) && (b2 == b3);
324 | };
325 | return goog.array.find(this.triangulation_, function(triA, iA, arrA) {
326 | return goog.array.find(other.triangulation_, function(triB, iB, arrB) {
327 | // any of the edges intersect (3x3 possibilities)
328 | if (lineInter(triA[0], triA[1], triB[0], triB[1]) ||
329 | lineInter(triA[0], triA[1], triB[1], triB[2]) ||
330 | lineInter(triA[0], triA[1], triB[2], triB[0]) ||
331 | lineInter(triA[1], triA[2], triB[0], triB[1]) ||
332 | lineInter(triA[1], triA[2], triB[1], triB[2]) ||
333 | lineInter(triA[1], triA[2], triB[2], triB[0]) ||
334 | lineInter(triA[2], triA[0], triB[0], triB[1]) ||
335 | lineInter(triA[2], triA[0], triB[1], triB[2]) ||
336 | lineInter(triA[2], triA[0], triB[2], triB[0])) return true;
337 |
338 | // all points of A are inside B
339 | if (isPointIn(triA[0], triB) &&
340 | isPointIn(triA[1], triB) &&
341 | isPointIn(triA[2], triB)) return true;
342 |
343 | // all points of B are inside A
344 | if (isPointIn(triB[0], triA) &&
345 | isPointIn(triB[1], triA) &&
346 | isPointIn(triB[2], triA)) return true;
347 |
348 | return false;
349 | }) !== null;
350 | }) !== null;
351 | };
352 |
353 |
354 | /**
355 | * Buffers the points into GPU buffer.
356 | * @private
357 | */
358 | weapi.Polygon.prototype.rebufferPoints_ = function() {
359 | var vertices = new Array();
360 |
361 | if (!this.head_) return;
362 | if (this.numVertices_ < 3) return;
363 | // recalc temporary ids
364 | /*var vrt = this.head_;
365 | var nextId = 0;
366 | do {
367 | vrt.tmpId = nextId++;
368 | vrt = vrt.next;
369 | } while (vrt != this.head_);
370 |
371 | goog.array.forEach(this.vertices_, function(el, i, arr) {
372 | if (!el) return;
373 | vertices[3 * el.tmpId + 0] = el.projX;
374 | vertices[3 * el.tmpId + 1] = el.projY;
375 | vertices[3 * el.tmpId + 2] = el.projZ;
376 | });
377 |
378 | var gl = this.gl;
379 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer_);
380 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);*/
381 |
382 | var cartoArray = [];
383 | var vrt = this.head_;
384 | do {
385 | cartoArray.push(new Cesium.Cartographic(vrt.x, vrt.y));
386 | vrt = vrt.next;
387 | } while (vrt != this.head_);
388 |
389 | var carteArray =
390 | Cesium.Ellipsoid.WGS84.cartographicArrayToCartesianArray(cartoArray);
391 | this.primitive.positions = carteArray;
392 | //this.primitive.update();
393 | carteArray.push(carteArray[0]);
394 | this.primitiveLine.positions = carteArray;
395 | };
396 |
397 |
398 | /**
399 | * @private
400 | */
401 | weapi.Polygon.prototype.solveTriangles_ = function() {
402 | //return;
403 | var n = this.numVertices_;
404 | this.roughArea_ = 0;
405 | this.triangulation_ = [];
406 |
407 | //this.context.sceneChanged = true;
408 |
409 | this.valid_ = false;
410 | //test intersection of segments
411 | if (n > 2) {
412 | //point p such that p<->p.next is not intersected by any other
413 | var cleanpoint = null;
414 | this.valid_ = true;
415 | var a = this.head_;
416 | do {
417 | var localValid = true;
418 | var b = a.next;
419 | var c = this.head_;
420 | do {
421 | var d = c.next;
422 | //test ab<->cd intersection
423 | var denom = (d.y - c.y) * (b.x - a.x) - (d.x - c.x) * (b.y - a.y);
424 | var p = ((d.x - c.x) * (a.y - c.y) - (d.y - c.y) * (a.x - c.x)) / denom;
425 | var t = ((b.x - a.x) * (a.y - c.y) - (b.y - a.y) * (a.x - c.x)) / denom;
426 |
427 | if (p > 0 && p < 1 && t > 0 && t < 1) {
428 | localValid = false;
429 | }
430 |
431 | c = d;
432 | } while (localValid && c != this.head_);
433 | if (localValid) cleanpoint = a;
434 | this.valid_ = this.valid_ && localValid;
435 | a = b;
436 | } while (a != this.head_);
437 |
438 | if (!this.valid_ && n == 4) {
439 | //although some lines intersect, we can still solve this for 4 points
440 | // by simply swapping some points (unrolling around the clean edge)
441 | var swapPoints = function(p1, p2) {
442 | var p1_prev = p1.prev;
443 | var p2_next = p2.next;
444 | p1.prev = p2;
445 | p2.next = p1;
446 |
447 | p1.next = p2_next;
448 | p2.prev = p1_prev;
449 |
450 | p2_next.prev = p1;
451 | p1_prev.next = p2;
452 | };
453 | if (cleanpoint) swapPoints(cleanpoint, cleanpoint.next);
454 |
455 | this.rebufferPoints_();
456 | this.valid_ = true;
457 | this.pointSwitchFlag_ = true;
458 | }
459 | }
460 | this.primitive.show = this.valid_;
461 | if (!this.valid_) return;
462 |
463 | var signedArea = 0;
464 | if (n > 0) {
465 | var a = this.head_;
466 | do {
467 | var b = a.next;
468 | signedArea += a.x * b.y - a.y * b.x;
469 | a = b;
470 | } while (a != this.head_);
471 | }
472 |
473 | //NOTE: this area is wrong, but the sign is correct
474 | if (signedArea > 0) {
475 | //CCW ! reverse the points
476 | this.pointSwitchFlag_ = true;
477 | for (var i = 0; i < this.vertices_.length; ++i) {
478 | var v = this.vertices_[i];
479 | if (v) {
480 | var tmp = v.next;
481 | v.next = v.prev;
482 | v.prev = tmp;
483 | }
484 | }
485 | this.rebufferPoints_();
486 | }
487 |
488 | var triangles = [];
489 | var addTriangle = goog.bind(function(v1, v2, v3) {
490 | triangles.push([v1.tmpId, v2.tmpId, v3.tmpId]);
491 | this.triangulation_.push([v1, v2, v3]);
492 |
493 | // Calculate triangle area using Heron's formula
494 | var len = function(u, v) {
495 | var x_ = u.projX - v.projX;
496 | var y_ = u.projY - v.projY;
497 | var z_ = u.projZ - v.projZ;
498 | return Math.sqrt(x_ * x_ + y_ * y_ + z_ * z_);
499 | };
500 | var a = len(v1, v2), b = len(v2, v3), c = len(v3, v1);
501 | var s = (a + b + c) / 2;
502 | var T = Math.sqrt(s * (s - a) * (s - b) * (s - c));
503 | this.roughArea_ += T;
504 | }, this);
505 |
506 | // Triangulation -- ear clipping method
507 | if (n < 3) {
508 | //triangles = [];
509 | } else if (n == 3) {
510 | addTriangle(this.head_, this.head_.prev, this.head_.next);
511 | } else {
512 | var head = this.head_;
513 |
514 | var Area2 = function(a, b, c) { //Calulates signed area via cross product
515 | return -((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y));
516 | };
517 |
518 | var Left = function(a, b, c) {return (Area2(a, b, c) > 0);}; //c left of ab
519 | var LeftOn = function(a, b, c) {return (Area2(a, b, c) >= 0);};
520 | var Collinear = function(a, b, c) {return (Area2(a, b, c) == 0);};
521 | var XOR = function(a, b) {return (a || b) && !(a && b);};
522 |
523 | var IntersectProp = function(a, b, c, d) { //Check proper intersection
524 | if (Collinear(a, b, c) || Collinear(a, b, d) ||
525 | Collinear(c, d, a) || Collinear(c, d, b))
526 | return false;
527 | return XOR(Left(a, b, c), Left(a, b, d)) &&
528 | XOR(Left(c, d, a), Left(c, d, b));
529 | };
530 |
531 | var InCone = function(a, b) { //Is line ab interal
532 | var a0 = a._prev, a1 = a._next;
533 | if (LeftOn(a, a1, a0))
534 | return Left(a, b, a0) && Left(b, a, a1);
535 | return !(LeftOn(a, b, a1) && LeftOn(b, a, a0));
536 | };
537 |
538 | var Diagonalie = function(a, b) {
539 | var c = head, c1;
540 | do {
541 | c1 = c._next;
542 | if ((c != a) && (c1 != a) && (c != b) && (c1 != b) &&
543 | IntersectProp(a, b, c, c1)) {
544 | return false;
545 | }
546 | c = c._next;
547 | } while (c != head);
548 | return true;
549 | };
550 |
551 | var Diagonal = function(a, b) {
552 | return InCone(a, b) && InCone(b, a) && Diagonalie(a, b);
553 | };
554 |
555 | var v0, v1, v2, v3, v4;
556 |
557 | goog.array.forEach(this.vertices_, function(el, i, arr) {
558 | if (el) {
559 | el._next = el.next;
560 | el._prev = el.prev;
561 | }
562 | });
563 |
564 | v1 = this.head_;
565 | do {
566 | v2 = v1._next;
567 | v0 = v1._prev;
568 | v1._ear = Diagonal(v0, v2);
569 | v1 = v1._next;
570 | } while (v1 != this.head_);
571 |
572 | var z = 99;
573 | while (z > 0 && n > 3) {
574 | z--;
575 | v2 = head;
576 | var y = 99;
577 | var broke;
578 | do {
579 | broke = false;
580 | if (v2._ear) {
581 | v3 = v2._next;
582 | v4 = v3._next;
583 | v1 = v2._prev;
584 | v0 = v1._prev;
585 | addTriangle(v3, v2, v1);
586 | v1._ear = Diagonal(v0, v3);
587 | v3._ear = Diagonal(v1, v4);
588 | v1._next = v3;
589 | v3._prev = v1;
590 | head = v3; //In case we cut out the head!
591 | n--;
592 | broke = true;
593 | }
594 | v2 = v2._next;
595 | y--;
596 | } while (y > 0 && !broke && v2 != head);
597 | }
598 | if (v1 && v3 && v4) {
599 | addTriangle(v4, v3, v1);
600 | }
601 | }
602 |
603 | this.roughArea_ *= weapi.utils.EARTH_RADIUS * weapi.utils.EARTH_RADIUS;
604 | };
605 |
606 |
607 |
608 | /**
609 | * @param {number} lat in degrees.
610 | * @param {number} lng in degrees.
611 | * @param {weapi.Polygon.Node=} opt_next .
612 | * @param {weapi.Polygon.Node=} opt_prev .
613 | * @constructor
614 | */
615 | weapi.Polygon.Node = function(lat, lng, opt_next, opt_prev) {
616 | /** @type {number} */
617 | this.x = 0;
618 | /** @type {number} */
619 | this.y = 0;
620 |
621 | /** @type {number} */
622 | this.projX = 0;
623 | /** @type {number} */
624 | this.projY = 0;
625 | /** @type {number} */
626 | this.projZ = 0;
627 |
628 | this.setLatLng(lat, lng);
629 |
630 | /** @type {?weapi.Polygon.Node} */
631 | this.next = opt_next || null;
632 | /** @type {?weapi.Polygon.Node} */
633 | this.prev = opt_prev || null;
634 |
635 | /** @type {number} */
636 | this.fixedId = -1;
637 | /** @type {number} */
638 | this.tmpId = -1;
639 |
640 | /** @type {boolean} */
641 | this._ear = false;
642 | /** @type {?weapi.Polygon.Node} */
643 | this._next = this.next;
644 | /** @type {?weapi.Polygon.Node} */
645 | this._prev = this.prev;
646 | };
647 |
648 |
649 | /**
650 | * @param {number} lat in degrees.
651 | * @param {number} lng in degrees.
652 | */
653 | weapi.Polygon.Node.prototype.setLatLng = function(lat, lng) {
654 | this.x = lng / 180 * Math.PI;
655 | this.y = lat / 180 * Math.PI;
656 |
657 | var cosy = Math.cos(this.y);
658 | this.projX = Math.sin(this.x) * cosy;
659 | this.projY = Math.sin(this.y);
660 | this.projZ = Math.cos(this.x) * cosy;
661 | };
662 |
--------------------------------------------------------------------------------
/src/polyicon.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.PolyIcon');
9 |
10 | goog.require('goog.dom');
11 | goog.require('weapi.markers.Popup');
12 |
13 |
14 |
15 | /**
16 | * @param {number} lat in radians.
17 | * @param {number} lng in radians.
18 | * @param {!weapi.App} app .
19 | * @constructor
20 | */
21 | weapi.PolyIcon = function(lat, lng, app) {
22 | /**
23 | * @type {!weapi.App}
24 | */
25 | this.app = app;
26 |
27 | /**
28 | * @type {?Cesium.Billboard}
29 | */
30 | this.billboard = null;
31 |
32 | /**
33 | * @type {number}
34 | * @private
35 | */
36 | this.lat_ = 0;
37 |
38 | /**
39 | * @type {number}
40 | * @private
41 | */
42 | this.lng_ = 0;
43 |
44 | this.setLatLng(lat, lng);
45 |
46 | /**
47 | * @type {string}
48 | */
49 | this.src = '';
50 | };
51 |
52 |
53 | /**
54 | * @define {number} Reference distance (in meters) for the icon size.
55 | */
56 | weapi.PolyIcon.REFERENCE_DISTANCE = 1000;
57 |
58 |
59 | /**
60 | * @define {number} Reference canvas height (in pixels).
61 | */
62 | weapi.PolyIcon.REFERENCE_CANVAS_HEIGHT = 768;
63 |
64 |
65 | /**
66 | * @param {number} lat in radians.
67 | * @param {number} lng in radians.
68 | */
69 | weapi.PolyIcon.prototype.setLatLng = function(lat, lng) {
70 | this.lat_ = lat;
71 | this.lng_ = lng;
72 |
73 | if (this.billboard) {
74 | var position = Cesium.Ellipsoid.WGS84.cartographicToCartesian(
75 | new Cesium.Cartographic(this.lng_, this.lat_));
76 |
77 | this.billboard.position = position;
78 | }
79 | };
80 |
81 |
82 | /**
83 | * @param {boolean} enable .
84 | */
85 | weapi.PolyIcon.prototype.enable = function(enable) {
86 | if (this.billboard) {
87 | this.billboard.show = enable;
88 | }
89 | };
90 |
91 |
92 | /**
93 | */
94 | weapi.PolyIcon.prototype.destroy = function() {
95 | if (this.billboard) {
96 | this.app.polyIconCollection.remove(this.billboard);
97 | this.billboard = null;
98 | }
99 | };
100 |
101 |
102 | /**
103 | * @param {string} src URL of the image to use.
104 | * @param {number} width Desired width of the image in meters.
105 | * @param {number} height Desired height of the image in meters.
106 | */
107 | weapi.PolyIcon.prototype.setImage = function(src, width, height) {
108 | if (src.length > 0) {
109 | if (!this.billboard) {
110 | this.billboard = this.app.polyIconCollection.add();
111 | this.setLatLng(this.lat_, this.lng_);
112 | }
113 | this.billboard.image = src;
114 | this.billboard.width = width;
115 | this.billboard.height = height;
116 | this.billboard.sizeInMeters = true;
117 | this.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
118 |
119 | this.setLatLng(this.lat_, this.lng_);
120 | this.app.sceneChanged = true;
121 | } else {
122 | if (this.billboard) {
123 | this.app.polyIconCollection.remove(this.billboard);
124 | this.billboard = null;
125 | }
126 | }
127 | };
128 |
129 |
130 | /**
131 | * @param {number} x .
132 | * @param {number} y .
133 | * @return {boolean} .
134 | */
135 | weapi.PolyIcon.prototype.isPointIn = function(x, y) {
136 | //TODO: (?)
137 | return false;
138 | };
139 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @author petr.sloup@klokantech.com (Petr Sloup)
4 | *
5 | * Copyright 2013 Klokan Technologies Gmbh (www.klokantech.com)
6 | */
7 |
8 | goog.provide('weapi.utils');
9 |
10 |
11 | /**
12 | * @const {number} Simplified Earth radius
13 | */
14 | weapi.utils.EARTH_RADIUS = 6378137;
15 |
16 |
17 | /**
18 | * TODO: fix for other ellipsoids
19 | * Calculates distance of two points on the surface.
20 | * @param {number} lat1 .
21 | * @param {number} lon1 .
22 | * @param {number} lat2 .
23 | * @param {number} lon2 .
24 | * @return {number} Calculated distance.
25 | */
26 | weapi.utils.calculateDistance = function(lat1, lon1, lat2, lon2) {
27 | var sindlathalf = Math.sin((lat2 - lat1) / 2);
28 | var sindlonhalf = Math.sin((lon2 - lon1) / 2);
29 | var a = sindlathalf * sindlathalf +
30 | Math.cos(lat1) * Math.cos(lat2) * sindlonhalf * sindlonhalf;
31 | var angle = 2 * Math.asin(Math.sqrt(a));
32 | return weapi.utils.EARTH_RADIUS * angle;
33 | };
34 |
35 |
36 | /**
37 | * @param {!weapi.App} app .
38 | * @param {number} lat Latitude in radians.
39 | * @param {number} lng Longitude in radians.
40 | * @param {number=} opt_alt Altitude in meters.
41 | * @return {Array.} Array [x, y, visibility] or null.
42 | */
43 | weapi.utils.getXYForLatLng = function(app, lat, lng, opt_alt) {
44 | var cam = app.camera.camera;
45 | var pos = new Cesium.Cartographic(lng, lat, opt_alt || 0);
46 | var cartes3 = Cesium.Ellipsoid.WGS84.cartographicToCartesian(pos);
47 | var cartes4 = new Cesium.Cartesian4(cartes3.x, cartes3.y, cartes3.z, 1);
48 |
49 | var mvp = app.scene.context.uniformState.modelViewProjection;
50 |
51 | var proj = Cesium.Matrix4.multiplyByVector(mvp, cartes4,
52 | new Cesium.Cartesian4());
53 |
54 | if (!goog.isDefAndNotNull(proj)) return null;
55 |
56 | var w = 1 / proj.w;
57 | var x = (proj.x * w + 1) / 2;
58 | var y = 1 - ((proj.y * w) + 1) / 2;
59 |
60 | var visibility = 1;
61 | if (x < -0.1 || x > 1.1 ||
62 | y < -0.1 || y > 1.1) {
63 | visibility = 0;
64 | } else {
65 | var direction = Cesium.Cartesian3.subtract(cartes3, cam.position, cartes3);
66 | var distance = Cesium.Cartesian3.magnitude(direction);
67 |
68 | var ldotc = -Cesium.Cartesian3.dot(
69 | Cesium.Cartesian3.normalize(direction, new Cesium.Cartesian3()),
70 | cam.position);
71 | var cdotc = Cesium.Cartesian3.dot(cam.position, cam.position);
72 |
73 | var r = weapi.utils.EARTH_RADIUS;
74 | var val = ldotc * ldotc - cdotc + r * r;
75 |
76 | var d1, d2;
77 | if (val < 0) {
78 | return null;
79 | } else {
80 | d1 = Math.min(ldotc + Math.sqrt(val), ldotc - Math.sqrt(val));
81 | d2 = Math.max(ldotc + Math.sqrt(val), ldotc - Math.sqrt(val));
82 | }
83 |
84 | visibility = (Math.abs(distance - d1) < Math.abs(distance - d2)) ? 1 : 0;
85 | }
86 |
87 | return [x * app.canvas.width, y * app.canvas.height, visibility];
88 | };
89 |
90 |
91 | /**
92 | * TODO: Use goog.style.installStyles after updating the closure-library.
93 | * @param {string} stylesString The style string to install.
94 | * @param {Node=} opt_node Node whose parent document should have the
95 | * styles installed.
96 | * @return {Element|StyleSheet} The style element created.
97 | */
98 | weapi.utils.installStyles = function(stylesString, opt_node) {
99 | var dh = goog.dom.getDomHelper(opt_node);
100 | var styleSheet = null;
101 |
102 | // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be
103 | // undefined as of IE 11.
104 | var doc = dh.getDocument();
105 | if (goog.userAgent.IE && doc.createStyleSheet) {
106 | styleSheet = doc.createStyleSheet();
107 | goog.style.setStyles(styleSheet, stylesString);
108 | } else {
109 | var head = dh.getElementsByTagNameAndClass('head')[0];
110 |
111 | // In opera documents are not guaranteed to have a head element, thus we
112 | // have to make sure one exists before using it.
113 | if (!head) {
114 | var body = dh.getElementsByTagNameAndClass('body')[0];
115 | head = dh.createDom('head');
116 | body.parentNode.insertBefore(head, body);
117 | }
118 | styleSheet = dh.createDom('style');
119 | // NOTE(user): Setting styles after the style element has been appended
120 | // to the head results in a nasty Webkit bug in certain scenarios. Please
121 | // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional
122 | // details.
123 | goog.style.setStyles(styleSheet, stylesString);
124 | dh.appendChild(head, styleSheet);
125 | }
126 | return styleSheet;
127 | };
128 |
--------------------------------------------------------------------------------