Compare commits
224 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3902759595 | ||
|
|
d71c733d9b | ||
|
|
90e1819d76 | ||
|
|
f84fae8f70 | ||
|
|
6c0dd6152d | ||
|
|
b96e70e952 | ||
|
|
501c90b0a2 | ||
|
|
2a2783c086 | ||
|
|
2f49876180 | ||
|
|
c7161acb6a | ||
|
|
db6314b6b2 | ||
|
|
65eb24da68 | ||
|
|
6652192647 | ||
|
|
bc34fe5b71 | ||
|
|
80b4473180 | ||
|
|
cd3b222467 | ||
|
|
6e4259359b | ||
|
|
a2e6119da1 | ||
|
|
c992cea9a4 | ||
|
|
ced327e9a0 | ||
|
|
df1fcc0d90 | ||
|
|
b13cbe8cb8 | ||
|
|
2abc88d41c | ||
|
|
3e0494aa9e | ||
|
|
aa5487b67b | ||
|
|
76333f72ba | ||
|
|
3ac08bcbef | ||
|
|
15a64e657d | ||
|
|
4f0431edd5 | ||
|
|
0f87df0ade | ||
|
|
1c21853269 | ||
|
|
1cd9256771 | ||
|
|
fcb95ced5f | ||
|
|
ffab886964 | ||
|
|
27cbbaf97b | ||
|
|
c0831cc64b | ||
|
|
c40263c38b | ||
|
|
daaf9695ff | ||
|
|
5d358a7367 | ||
|
|
80abb8fb15 | ||
|
|
e63bb45e6f | ||
|
|
edc02dbde2 | ||
|
|
4462608991 | ||
|
|
ff3cc9b4d0 | ||
|
|
a844691d7b | ||
|
|
acf973751b | ||
|
|
e843b2cfc0 | ||
|
|
719495587c | ||
|
|
9e010631c1 | ||
|
|
a64f2eb720 | ||
|
|
d643951a74 | ||
|
|
a39f751981 | ||
|
|
fc98704bcb | ||
|
|
27c530ec64 | ||
|
|
e3f7d29bb2 | ||
|
|
44347a9ed3 | ||
|
|
31dae929f5 | ||
|
|
5712792772 | ||
|
|
bcf7649b9a | ||
|
|
7e4119d580 | ||
|
|
72903d6454 | ||
|
|
35af80b433 | ||
|
|
ad6d91b9ae | ||
|
|
37cbb8e43e | ||
|
|
e0329febc4 | ||
|
|
cff00f1c3b | ||
|
|
61c4fa8cd6 | ||
|
|
cc24ec1be6 | ||
|
|
2b0256140e | ||
|
|
88bbb8c1ae | ||
|
|
e60dc93e0e | ||
|
|
599835e818 | ||
|
|
afa688273b | ||
|
|
6b4e00e6ac | ||
|
|
10379920d2 | ||
|
|
ebae0386bd | ||
|
|
bf7af07657 | ||
|
|
327fe11344 | ||
|
|
319efedb14 | ||
|
|
4fe16d0281 | ||
|
|
ce8b63a67e | ||
|
|
6b25f713cd | ||
|
|
4b79818797 | ||
|
|
062614388c | ||
|
|
8c5be52462 | ||
|
|
b6d694050e | ||
|
|
fc555241ea | ||
|
|
33b0f88771 | ||
|
|
1fe5a68e16 | ||
|
|
948003ff27 | ||
|
|
d837166a1b | ||
|
|
c659c05de8 | ||
|
|
485ade42b5 | ||
|
|
85c3aae454 | ||
|
|
a29fc016f5 | ||
|
|
19eadaea24 | ||
|
|
b8e8d30df0 | ||
|
|
f43637cc33 | ||
|
|
56faf4c3ad | ||
|
|
a0b271a812 | ||
|
|
a1c00744d1 | ||
|
|
94b8cf6af3 | ||
|
|
0c0c8c5d56 | ||
|
|
fef4d4e9b7 | ||
|
|
8e4fbe3e74 | ||
|
|
110c17cf9e | ||
|
|
e38250ee14 | ||
|
|
39012a58f8 | ||
|
|
7b66b294a8 | ||
|
|
9ee93cd2cf | ||
|
|
ed18310136 | ||
|
|
a3c137cff1 | ||
|
|
9eb4816b27 | ||
|
|
ee653a8e0d | ||
|
|
7edd10d66f | ||
|
|
e0cab3d3ba | ||
|
|
34ed3c684e | ||
|
|
d0811ea286 | ||
|
|
54c5330203 | ||
|
|
09768c6634 | ||
|
|
dfdb498d0d | ||
|
|
d1aec83ca2 | ||
|
|
2937513cb3 | ||
|
|
b46e985af0 | ||
|
|
e50662f0a1 | ||
|
|
bcdb191de2 | ||
|
|
775bf82631 | ||
|
|
fc9123947d | ||
|
|
636c65da69 | ||
|
|
adcbd0bb50 | ||
|
|
89c0eaef8d | ||
|
|
889eef66c1 | ||
|
|
de851e9b29 | ||
|
|
cb6b716b98 | ||
|
|
2980a0a168 | ||
|
|
829aac1860 | ||
|
|
3d351a53e6 | ||
|
|
a0ceebfb2f | ||
|
|
a9d93fe151 | ||
|
|
b4257779f1 | ||
|
|
0f3778ce00 | ||
|
|
36a366bfeb | ||
|
|
95bcb574b7 | ||
|
|
bd5510b62a | ||
|
|
a34e7ddd99 | ||
|
|
1de0d09acb | ||
|
|
2b1417df3f | ||
|
|
cfac31799b | ||
|
|
61fc71c30f | ||
|
|
9f3052211b | ||
|
|
4450981b0d | ||
|
|
54884314ee | ||
|
|
a75ea2cc6b | ||
|
|
aba93e7b9c | ||
|
|
90d6a6f441 | ||
|
|
7454df0c2f | ||
|
|
a126702e95 | ||
|
|
dd788b406c | ||
|
|
c97a26ae26 | ||
|
|
f475387d39 | ||
|
|
5af4dfa623 | ||
|
|
e3666f18da | ||
|
|
046b78cca7 | ||
|
|
b349058c58 | ||
|
|
8a53947f44 | ||
|
|
9acba8f82a | ||
|
|
0cb9d73848 | ||
|
|
7069e2688d | ||
|
|
0c72fce1ff | ||
|
|
87414256e2 | ||
|
|
9e7e15ed5c | ||
|
|
68850c7ddc | ||
|
|
f277206631 | ||
|
|
2a2cc40d42 | ||
|
|
395b59c01e | ||
|
|
58b4b6eeea | ||
|
|
5b1106ac23 | ||
|
|
2483fbd26b | ||
|
|
736ef88473 | ||
|
|
873999ec7d | ||
|
|
cbf0ecfd75 | ||
|
|
97ded06ee4 | ||
|
|
782ff6e9f5 | ||
|
|
dc2137b622 | ||
|
|
c5eb80e610 | ||
|
|
d1ee300968 | ||
|
|
b44a6ab26a | ||
|
|
3cb0aae796 | ||
|
|
490bef8d40 | ||
|
|
f477fc18f2 | ||
|
|
3c243b0236 | ||
|
|
e9785317eb | ||
|
|
315695eeb8 | ||
|
|
f20db28901 | ||
|
|
2ce14a50a1 | ||
|
|
057cc92716 | ||
|
|
5786121cd5 | ||
|
|
b727e4f45e | ||
|
|
fb9673465a | ||
|
|
babdce4819 | ||
|
|
684e305bc8 | ||
|
|
deb541791a | ||
|
|
db49842f63 | ||
|
|
8fe8302dc2 | ||
|
|
b1a9f765fc | ||
|
|
db583be0f9 | ||
|
|
d0cd1064ff | ||
|
|
b4a996e760 | ||
|
|
dee114d4c4 | ||
|
|
db34a338d6 | ||
|
|
0eac5007e8 | ||
|
|
c1921a6b16 | ||
|
|
dc28590cff | ||
|
|
3ab32ff744 | ||
|
|
daaaff5ac7 | ||
|
|
fa0258a323 | ||
|
|
4f83392b76 | ||
|
|
8a04f5059d | ||
|
|
92f70c225f | ||
|
|
41b7893523 | ||
|
|
89becd4c6d | ||
|
|
7eb60fae23 | ||
|
|
d08c0baff9 | ||
|
|
b045e4ee5b |
7
.dependabot/config.yml
Normal file
7
.dependabot/config.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# See https://dependabot.com/docs/config-file/
|
||||||
|
version: 1
|
||||||
|
update_configs:
|
||||||
|
- package_manager: "javascript"
|
||||||
|
directory: "/"
|
||||||
|
update_schedule: "weekly"
|
||||||
|
version_requirement_updates: "increase_versions_if_necessary"
|
||||||
2
.github/stale.yml
vendored
2
.github/stale.yml
vendored
@@ -8,6 +8,8 @@ exemptLabels:
|
|||||||
- regression
|
- regression
|
||||||
- bug
|
- bug
|
||||||
- 'pull request accepted'
|
- 'pull request accepted'
|
||||||
|
# Issues assigned to a milestone will not go stale
|
||||||
|
exemptMilestones: true
|
||||||
# Label to use when marking an issue as stale
|
# Label to use when marking an issue as stale
|
||||||
staleLabel: stale
|
staleLabel: stale
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
|||||||
86
changelog/v6.1.0.md
Normal file
86
changelog/v6.1.0.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# 6.1.0
|
||||||
|
|
||||||
|
With 47 pull requests, this release keeps up the momentum of the v6.x effort and brings several bug fixes, performance improvements and new features. In addition to that, we added some missing documentation to our API docs and gave them some usability improvements.
|
||||||
|
|
||||||
|
## New features
|
||||||
|
|
||||||
|
* A new, performance optimized hit detection API on the layer level, `Layer#getFeatures(pixel)` has been introduced. At this point it is implemented for vector, vector tile and image vector layers, and is recommended when performance is critical, e.g. for hit detection on mouseover.
|
||||||
|
* For the WebGLPoints layer, we made several additions to the style expressions system. This includes a `case` operator, support for array and string types, and type checking.
|
||||||
|
|
||||||
|
## List of all changes
|
||||||
|
|
||||||
|
* [#10214](https://github.com/openlayers/openlayers/pull/10214) - WebGL / Add 'case' operator for style expressions ([@jahow](https://github.com/jahow))
|
||||||
|
* [#10198](https://github.com/openlayers/openlayers/pull/10198) - WebGL renderer / use the specified loading strategy for the vector data ([@jahow](https://github.com/jahow))
|
||||||
|
* [#10212](https://github.com/openlayers/openlayers/pull/10212) - Add config file for dependabot ([@fredj](https://github.com/fredj))
|
||||||
|
* [#10187](https://github.com/openlayers/openlayers/pull/10187) - Set touch-action to allow native touch gestures ([@ahocevar](https://github.com/ahocevar))
|
||||||
|
* [#10194](https://github.com/openlayers/openlayers/pull/10194) - Added check to make sure extent array has four objects. ([@MrSoUndso](https://github.com/MrSoUndso))
|
||||||
|
* [#10204](https://github.com/openlayers/openlayers/pull/10204) - Avoid scrolling over navigation ([@tschaub](https://github.com/tschaub))
|
||||||
|
* [#10205](https://github.com/openlayers/openlayers/pull/10205) - Add the default to the type column ([@tschaub](https://github.com/tschaub))
|
||||||
|
* [#10178](https://github.com/openlayers/openlayers/pull/10178) - Make ol/interaction/Select#handleEvent return always true. ([@TieSKey](https://github.com/TieSKey))
|
||||||
|
* [#10203](https://github.com/openlayers/openlayers/pull/10203) - Keep issues assigned to a milestone from going stale ([@tschaub](https://github.com/tschaub))
|
||||||
|
* [#10200](https://github.com/openlayers/openlayers/pull/10200) - Carbon add on API docs ([@tschaub](https://github.com/tschaub))
|
||||||
|
* [#10196](https://github.com/openlayers/openlayers/pull/10196) - Webgl / add support for string and arrays in style expressions ([@jahow](https://github.com/jahow))
|
||||||
|
* [#10197](https://github.com/openlayers/openlayers/pull/10197) - Restore node v8 compatibility ([@ahocevar](https://github.com/ahocevar))
|
||||||
|
* [#10195](https://github.com/openlayers/openlayers/pull/10195) - Treat hsl/hsla as named colors ([@mike-000](https://github.com/mike-000))
|
||||||
|
* [#10188](https://github.com/openlayers/openlayers/pull/10188) - Document that map render events do not have a context set ([@ahocevar](https://github.com/ahocevar))
|
||||||
|
* [#10184](https://github.com/openlayers/openlayers/pull/10184) - Clamp the weight value between 0 and 1 ([@fredj](https://github.com/fredj))
|
||||||
|
* [#10175](https://github.com/openlayers/openlayers/pull/10175) - Remove description from type tag ([@tschaub](https://github.com/tschaub))
|
||||||
|
* [#10176](https://github.com/openlayers/openlayers/pull/10176) - Avoid toggling when clicking the title ([@tschaub](https://github.com/tschaub))
|
||||||
|
* [#10172](https://github.com/openlayers/openlayers/pull/10172) - Ensure inherited docs are shown ([@ahocevar](https://github.com/ahocevar))
|
||||||
|
* [#10171](https://github.com/openlayers/openlayers/pull/10171) - Open default attributions links in a new tab or window ([@mike-000](https://github.com/mike-000))
|
||||||
|
* [#10047](https://github.com/openlayers/openlayers/pull/10047) - Apidoc changes ([@KaiVolland](https://github.com/KaiVolland))
|
||||||
|
* [#10161](https://github.com/openlayers/openlayers/pull/10161) - Implement getFeatures() for ol/layer/VectorImage ([@ahocevar](https://github.com/ahocevar))
|
||||||
|
* [#10168](https://github.com/openlayers/openlayers/pull/10168) - Webgl / Add support for filtering and color interpolation in style expressions ([@jahow](https://github.com/jahow))
|
||||||
|
* [#10170](https://github.com/openlayers/openlayers/pull/10170) - Add back html from select-features example ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10093](https://github.com/openlayers/openlayers/pull/10093) - Set label text for Graticule option styles ([@mike-000](https://github.com/mike-000))
|
||||||
|
* [#10160](https://github.com/openlayers/openlayers/pull/10160) - Improve test in case of async call ([@ejn](https://github.com/ejn))
|
||||||
|
* [#10159](https://github.com/openlayers/openlayers/pull/10159) - Fix target name collision in event.Target and Control ([@ejn](https://github.com/ejn))
|
||||||
|
* [#10142](https://github.com/openlayers/openlayers/pull/10142) - Fix tilechange events ([@ahocevar](https://github.com/ahocevar))
|
||||||
|
* [#10138](https://github.com/openlayers/openlayers/pull/10138) - Should be able to ovveride defaultvalue of style in selection. ([@wirdehall](https://github.com/wirdehall))
|
||||||
|
* [#10135](https://github.com/openlayers/openlayers/pull/10135) - Revise the description of applyTransform for geometries ([@mike-000](https://github.com/mike-000))
|
||||||
|
* [#10133](https://github.com/openlayers/openlayers/pull/10133) - Properly unregister source tile change listeners ([@ahocevar](https://github.com/ahocevar))
|
||||||
|
* [#10094](https://github.com/openlayers/openlayers/pull/10094) - New Layer#getFeatures method with fast hit detection ([@ahocevar](https://github.com/ahocevar))
|
||||||
|
* [#10104](https://github.com/openlayers/openlayers/pull/10104) - Remove font sizing from the attribution control ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10108](https://github.com/openlayers/openlayers/pull/10108) - Fix documentation for handleMoveEvent ([@notnotse](https://github.com/notnotse))
|
||||||
|
* [#10118](https://github.com/openlayers/openlayers/pull/10118) - Document the OSM crossOrigin default ([@mike-000](https://github.com/mike-000))
|
||||||
|
* [#10112](https://github.com/openlayers/openlayers/pull/10112) - No ERROR state for render tiles ([@ahocevar](https://github.com/ahocevar))
|
||||||
|
* [#10115](https://github.com/openlayers/openlayers/pull/10115) - Improve Heatmap hit detection test ([@jahow](https://github.com/jahow))
|
||||||
|
* [#10111](https://github.com/openlayers/openlayers/pull/10111) - Correct the defaulting of ol/source/VectorTile maxZoom ([@mike-000](https://github.com/mike-000))
|
||||||
|
* [#10102](https://github.com/openlayers/openlayers/pull/10102) - Cosmetic type fixes ([@KlausBenndorf](https://github.com/KlausBenndorf))
|
||||||
|
* [#10107](https://github.com/openlayers/openlayers/pull/10107) - Update Vector Tile Info example test for no features ([@mike-000](https://github.com/mike-000))
|
||||||
|
* [#10097](https://github.com/openlayers/openlayers/pull/10097) - Align layer canvas to the left ([@fredj](https://github.com/fredj))
|
||||||
|
* [#10101](https://github.com/openlayers/openlayers/pull/10101) - Add hit detection support on the Heatmap layer ([@jahow](https://github.com/jahow))
|
||||||
|
* [#10087](https://github.com/openlayers/openlayers/pull/10087) - Add example showing vector tiles in EPSG:4326 ([@petrsloup](https://github.com/petrsloup))
|
||||||
|
* [#10086](https://github.com/openlayers/openlayers/pull/10086) - Update mapbox-style example ([@petrsloup](https://github.com/petrsloup))
|
||||||
|
* [#10083](https://github.com/openlayers/openlayers/pull/10083) - webgl points layer example fixes ([@MoonE](https://github.com/MoonE))
|
||||||
|
* [#10076](https://github.com/openlayers/openlayers/pull/10076) - Remove unnecessary typecast ([@fredj](https://github.com/fredj))
|
||||||
|
* [#10074](https://github.com/openlayers/openlayers/pull/10074) - Remove offsets from mouse position calculation ([@mike-000](https://github.com/mike-000))
|
||||||
|
* [#10072](https://github.com/openlayers/openlayers/pull/10072) - Better typing ([@fredj](https://github.com/fredj))
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Dependency Updates</summary>
|
||||||
|
|
||||||
|
* [#10208](https://github.com/openlayers/openlayers/pull/10208) - Bump pixelmatch from 5.0.2 to 5.1.0 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10210](https://github.com/openlayers/openlayers/pull/10210) - Bump glob from 7.1.4 to 7.1.5 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10209](https://github.com/openlayers/openlayers/pull/10209) - Bump karma from 4.1.0 to 4.4.1 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10207](https://github.com/openlayers/openlayers/pull/10207) - Bump ol-mapbox-style from 5.0.0-beta.3 to 5.0.2 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10157](https://github.com/openlayers/openlayers/pull/10157) - Bump karma-coverage-istanbul-reporter from 2.0.5 to 2.1.0 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10155](https://github.com/openlayers/openlayers/pull/10155) - Bump @types/pbf from 3.0.1 to 3.0.2 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10153](https://github.com/openlayers/openlayers/pull/10153) - Bump yargs from 14.0.0 to 14.2.0 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10154](https://github.com/openlayers/openlayers/pull/10154) - Bump @babel/core from 7.4.4 to 7.6.4 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10151](https://github.com/openlayers/openlayers/pull/10151) - Bump rollup-plugin-babel from 4.3.2 to 4.3.3 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10148](https://github.com/openlayers/openlayers/pull/10148) - Bump rollup-plugin-node-resolve from 5.0.0 to 5.2.0 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10150](https://github.com/openlayers/openlayers/pull/10150) - Bump copy-webpack-plugin from 5.0.3 to 5.0.4 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10149](https://github.com/openlayers/openlayers/pull/10149) - Bump rbush from 3.0.0 to 3.0.1 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10147](https://github.com/openlayers/openlayers/pull/10147) - Bump rollup from 1.12.0 to 1.25.1 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10145](https://github.com/openlayers/openlayers/pull/10145) - Greenkeeper/handlebars 4.4.5 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10132](https://github.com/openlayers/openlayers/pull/10132) - Update webpack to the latest version 🚀 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10143](https://github.com/openlayers/openlayers/pull/10143) - Update mocha to the latest version 🚀 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10117](https://github.com/openlayers/openlayers/pull/10117) - Update pbf to the latest version 🚀 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10114](https://github.com/openlayers/openlayers/pull/10114) - Update coveralls to the latest version 🚀 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10116](https://github.com/openlayers/openlayers/pull/10116) - Update webpack to the latest version 🚀 ([@openlayers](https://github.com/openlayers))
|
||||||
|
* [#10105](https://github.com/openlayers/openlayers/pull/10105) - Update jsdoc-plugin-typescript to the latest version 🚀 ([@openlayers](https://github.com/openlayers))
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
@navWidth: 250px;
|
|
||||||
@colorSubtitle: rgb(119, 156, 52);
|
|
||||||
@colorRed: rgb(238, 125, 125);
|
|
||||||
@colorLink: #2a6496;
|
|
||||||
@colorBgNavi: #2a2a2a;
|
|
||||||
|
|
||||||
.font-description () {
|
|
||||||
font-family: "freight-text-pro",Georgia,Cambria,"Times New Roman",Times,serif
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
@import "common.less";
|
|
||||||
|
|
||||||
footer {
|
|
||||||
margin: 15px 0;
|
|
||||||
padding-top: 15px;
|
|
||||||
border-top: 1px solid #e1e1e1;
|
|
||||||
.font-description();
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
@import "common.less";
|
|
||||||
|
|
||||||
// normalize
|
|
||||||
html, body {
|
|
||||||
font: 1em "jaf-bernino-sans","Lucida Grande","Lucida Sans Unicode","Lucida Sans",Geneva,Verdana,sans-serif;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
ul, ol {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#wrap {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
@import "navigation.less";
|
|
||||||
@import "main.less";
|
|
||||||
@import "footer.less";
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
@import "common.less";
|
|
||||||
|
|
||||||
.main {
|
|
||||||
padding: 20px 20px;
|
|
||||||
margin-left: @navWidth;
|
|
||||||
.page-title {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.6em;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 12px;
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4.name {
|
|
||||||
span.type-signature {
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: gray;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 0.7em;
|
|
||||||
padding: 2px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.type {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.glyphicon {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
color: #e1e1e1;
|
|
||||||
margin-left: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.returnType {
|
|
||||||
margin-left: 3px;
|
|
||||||
background-color: transparent!important;
|
|
||||||
color: gray!important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
span.static {
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: @colorSubtitle!important;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 0.7em;
|
|
||||||
padding: 2px 4px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.number {
|
|
||||||
background-color: #ccc!important;
|
|
||||||
color: #2fa2b1!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.string {
|
|
||||||
background-color: #ccc!important;
|
|
||||||
color: #2fa2b1!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.object {
|
|
||||||
background-color: #ccc!important;
|
|
||||||
color: #2fa2b1!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.array {
|
|
||||||
background-color: #ccc!important;
|
|
||||||
color: #2fa2b1!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.boolean {
|
|
||||||
background-color: #ccc!important;
|
|
||||||
color: #2fa2b1!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subsection-title {
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 30px;
|
|
||||||
color: @colorSubtitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
margin-top: 10px;
|
|
||||||
// .font-description();
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-source {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
dt.tag-source {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt.tag-todo {
|
|
||||||
font-size: 10px;
|
|
||||||
display: inline-block;
|
|
||||||
background-color: @colorLink;
|
|
||||||
color: #fff;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-signature {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-deprecated {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.important {
|
|
||||||
background-color: @colorRed;
|
|
||||||
color: #fff;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nameContainer {
|
|
||||||
position: relative;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 5px;
|
|
||||||
border-top: 1px solid #e1e1e1;
|
|
||||||
|
|
||||||
.inherited {
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #888!important;
|
|
||||||
font-size: 0.7em;
|
|
||||||
padding: 2px 4px;
|
|
||||||
margin-right: 5px;
|
|
||||||
a {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-source {
|
|
||||||
position: absolute;
|
|
||||||
top: 17px;
|
|
||||||
right: 0;
|
|
||||||
font-size: 10px;
|
|
||||||
a {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.inherited {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin-right: 150px;
|
|
||||||
line-height: 1.3;
|
|
||||||
|
|
||||||
.signature {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: normal;
|
|
||||||
font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
|
|
||||||
th {
|
|
||||||
padding: 3px 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
vertical-align: top;
|
|
||||||
padding: 5px 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
width: 110px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type {
|
|
||||||
width: 60px;
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attributes {
|
|
||||||
width: 80px;
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
font-size: 12px;
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.optional {
|
|
||||||
float: left;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #ddd!important;
|
|
||||||
font-size: 0.7em;
|
|
||||||
padding: 2px 4px;
|
|
||||||
margin-right: 5px;
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.readme {
|
|
||||||
p {
|
|
||||||
margin-top: 15px;
|
|
||||||
line-height: 1.2;
|
|
||||||
font-size: 0.85em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin-top: 30px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
border-bottom: 1px solid #e1e1e1;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
article {
|
|
||||||
ol, ul {
|
|
||||||
margin-left: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol > li {
|
|
||||||
list-style-type: decimal;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul > li {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
list-style-type: disc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
@import "common.less";
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background-color: gray;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navigation {
|
|
||||||
position: fixed;
|
|
||||||
float: left;
|
|
||||||
width: @navWidth;
|
|
||||||
height: 100%;
|
|
||||||
background-color: @colorBgNavi;
|
|
||||||
|
|
||||||
.applicationName {
|
|
||||||
margin: 0;
|
|
||||||
margin-top: 15px;
|
|
||||||
padding: 10px 15px;
|
|
||||||
font: bold 1.25em Helvetica;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
padding: 10px 15px;
|
|
||||||
|
|
||||||
input {
|
|
||||||
background-color: #333;
|
|
||||||
color: #fff;
|
|
||||||
border-color: #555;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
|
||||||
padding: 10px 15px 0 15px;
|
|
||||||
position: relative;
|
|
||||||
overflow: auto;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.item {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
border-bottom: 1px solid #333;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #bbb;
|
|
||||||
&:hover {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
a {
|
|
||||||
color: #e1e1e1;
|
|
||||||
&:hover {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
display: block;
|
|
||||||
font-size: 0.8em;
|
|
||||||
|
|
||||||
.static {
|
|
||||||
display: block;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: @colorSubtitle;
|
|
||||||
color: #000;
|
|
||||||
font-size: 0.7em;
|
|
||||||
padding: 2px 4px;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
margin-top: 10px;
|
|
||||||
font: bold 0.65em Helvetica;
|
|
||||||
color: @colorSubtitle;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ul {
|
|
||||||
& > li {
|
|
||||||
font-size: 0.7em;
|
|
||||||
padding-left: 8px;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemMembers {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -215,15 +215,20 @@ function buildNav(members) {
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
function createEntry(type, v) {
|
_.each(merged, function(v) {
|
||||||
return {
|
// exclude interfaces from sidebar
|
||||||
type: type,
|
if (v.interface !== true && v.kind === 'class') {
|
||||||
|
nav.push({
|
||||||
|
type: 'class',
|
||||||
longname: v.longname,
|
longname: v.longname,
|
||||||
|
prettyname: v.longname
|
||||||
|
.split('~')[0]
|
||||||
|
.replace('module:', ''),
|
||||||
name: v.name,
|
name: v.name,
|
||||||
classes: find({
|
module: find({
|
||||||
kind: 'class',
|
kind: 'module',
|
||||||
memberof: v.longname
|
longname: v.memberof
|
||||||
}).map(createEntry.bind(this, 'class')),
|
})[0],
|
||||||
members: find({
|
members: find({
|
||||||
kind: 'member',
|
kind: 'member',
|
||||||
memberof: v.longname
|
memberof: v.longname
|
||||||
@@ -236,17 +241,48 @@ function buildNav(members) {
|
|||||||
kind: 'typedef',
|
kind: 'typedef',
|
||||||
memberof: v.longname
|
memberof: v.longname
|
||||||
}),
|
}),
|
||||||
|
fires: v.fires,
|
||||||
events: find({
|
events: find({
|
||||||
kind: 'event',
|
kind: 'event',
|
||||||
memberof: v.longname
|
memberof: v.longname
|
||||||
})
|
})
|
||||||
};
|
});
|
||||||
}
|
} else if (v.kind == 'module') {
|
||||||
_.each(merged, function(v) {
|
const classes = find({
|
||||||
// exclude interfaces from sidebar
|
kind: 'class',
|
||||||
if (v.interface !== true) {
|
memberof: v.longname
|
||||||
if (v.kind == 'module') {
|
});
|
||||||
nav.push(createEntry('module', v));
|
const members = find({
|
||||||
|
kind: 'member',
|
||||||
|
memberof: v.longname
|
||||||
|
});
|
||||||
|
const methods = find({
|
||||||
|
kind: 'function',
|
||||||
|
memberof: v.longname
|
||||||
|
});
|
||||||
|
const typedefs = find({
|
||||||
|
kind: 'typedef',
|
||||||
|
memberof: v.longname
|
||||||
|
});
|
||||||
|
const events = find({
|
||||||
|
kind: 'event',
|
||||||
|
memberof: v.longname
|
||||||
|
});
|
||||||
|
// only add modules that have more to show than just a single class
|
||||||
|
if (classes.length !== 1 && (classes.length + members.length + methods.length + typedefs.length + events.length > 0)) {
|
||||||
|
nav.push({
|
||||||
|
type: 'module',
|
||||||
|
longname: v.longname,
|
||||||
|
prettyname: v.longname
|
||||||
|
.split('~')[0]
|
||||||
|
.replace('module:', ''),
|
||||||
|
name: v.name,
|
||||||
|
members: members,
|
||||||
|
methods: methods,
|
||||||
|
typedefs: typedefs,
|
||||||
|
fires: v.fires,
|
||||||
|
events: events
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,37 +1,74 @@
|
|||||||
$(function () {
|
$(function () {
|
||||||
|
// Search Items
|
||||||
|
$('#include_modules').change(function (e) {
|
||||||
|
console.log('change');
|
||||||
|
if ($(this).is(':checked')) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var getSearchWeight = function (searchTerm, $matchedItem) {
|
||||||
|
let weight = 0;
|
||||||
|
// We could get smarter on the weight here
|
||||||
|
if ($matchedItem.data('shortname')
|
||||||
|
&& $matchedItem.data('shortname').toLowerCase() === searchTerm.toLowerCase()) {
|
||||||
|
weight++;
|
||||||
|
}
|
||||||
|
return weight;
|
||||||
|
};
|
||||||
|
|
||||||
|
// sort function callback
|
||||||
|
var weightSorter = function (a, b) {
|
||||||
|
var aW = $(a).data('weight') || 0;
|
||||||
|
var bW = $(b).data('weight') || 0;
|
||||||
|
return bW - aW;
|
||||||
|
};
|
||||||
|
|
||||||
// Search Items
|
// Search Items
|
||||||
$('#search').on('keyup', function (e) {
|
$('#search').on('keyup', function (e) {
|
||||||
var value = $(this).val();
|
var value = $(this).val();
|
||||||
var $el = $('.navigation');
|
var $el = $('.navigation');
|
||||||
|
|
||||||
if (value) {
|
if (value && value.length > 1) {
|
||||||
var regexp = new RegExp(value, 'i');
|
var regexp = new RegExp(value, 'i');
|
||||||
$el.find('li, .itemMembers').hide();
|
$el.find('li, .itemMembers').hide();
|
||||||
|
|
||||||
$el.find('li').each(function (i, v) {
|
$el.find('li').each(function (i, v) {
|
||||||
var $item = $(v);
|
const $item = $(v);
|
||||||
|
const name = $item.data('name');
|
||||||
|
|
||||||
if ($item.data('name') && regexp.test($item.data('name'))) {
|
if (name && regexp.test(name)) {
|
||||||
const container = $item.parent().parent().parent();
|
const $classEntry = $item.closest('.item');
|
||||||
container.show();
|
const $members = $item.closest('.itemMembers');
|
||||||
container.closest('.itemMembers').show();
|
|
||||||
container.closest('.item').show();
|
// Do the weight thing
|
||||||
|
$classEntry.removeData('weight');
|
||||||
|
$classEntry.show();
|
||||||
|
const weight = getSearchWeight(value, $classEntry);
|
||||||
|
$classEntry.data('weight', weight);
|
||||||
|
|
||||||
|
$members.show();
|
||||||
|
$classEntry.show();
|
||||||
$item.show();
|
$item.show();
|
||||||
$item.closest('.itemMembers').show();
|
|
||||||
$item.closest('.item').show();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(".navigation ul.list li.item:visible")
|
||||||
|
.sort(weightSorter) // sort elements
|
||||||
|
.appendTo(".navigation ul.list"); // append again to the list
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$el.find('.item, .itemMembers').hide();
|
$el.find('.item, .itemMembers').show();
|
||||||
$('.navigation>ul>li').show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$el.find('.list').scrollTop(0);
|
$el.find('.list').scrollTop(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle when click an item element
|
// Toggle when click an item element
|
||||||
$('.navigation').on('click', '.title', function (e) {
|
$('.navigation').on('click', '.toggle', function (e) {
|
||||||
$(this).parent().find('.itemMembers').toggle();
|
$(this).parent().parent().find('.itemMembers').toggle();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show an item related a current documentation automatically
|
// Show an item related a current documentation automatically
|
||||||
@@ -75,7 +112,7 @@ $(function () {
|
|||||||
var branchSearch = url.match(/\/([^\/]*)\/apidoc\//);
|
var branchSearch = url.match(/\/([^\/]*)\/apidoc\//);
|
||||||
var cookieText = 'dismissed=-' + latestVersion + '-';
|
var cookieText = 'dismissed=-' + latestVersion + '-';
|
||||||
var dismissed = document.cookie.indexOf(cookieText) != -1;
|
var dismissed = document.cookie.indexOf(cookieText) != -1;
|
||||||
if (!dismissed && /^v[0-9\.]*$/.test(branchSearch[1]) && currentVersion != latestVersion) {
|
if (branchSearch && !dismissed && /^v[0-9\.]*$/.test(branchSearch[1]) && currentVersion != latestVersion) {
|
||||||
var link = url.replace(branchSearch[0], '/latest/apidoc/');
|
var link = url.replace(branchSearch[0], '/latest/apidoc/');
|
||||||
fetch(link, {method: 'head'}).then(function(response) {
|
fetch(link, {method: 'head'}).then(function(response) {
|
||||||
var a = document.getElementById('latest-link');
|
var a = document.getElementById('latest-link');
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
.navbar-inverse .navbar-nav>li>a:hover,
|
.navbar-inverse .navbar-nav>li>a:hover,
|
||||||
.navbar-inverse .navbar-nav>li>a:focus,
|
.navbar-inverse .navbar-nav>li>a:focus,
|
||||||
.navbar-inverse .navbar-nav>li>a.active
|
.navbar-inverse .navbar-nav>li>a.active
|
||||||
{
|
{
|
||||||
outline:0;
|
outline:0;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #268591;
|
background-color: #268591;
|
||||||
@@ -106,6 +106,15 @@ li {
|
|||||||
.navigation .applicationName a {
|
.navigation .applicationName a {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
.navigation .include-modules {
|
||||||
|
color: #e1e1e1;
|
||||||
|
float: right;
|
||||||
|
font-size: 0.75em;
|
||||||
|
padding: 5px 15px;
|
||||||
|
}
|
||||||
|
.navigation .include-modules input {
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
.navigation .search {
|
.navigation .search {
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
}
|
}
|
||||||
@@ -125,6 +134,11 @@ li {
|
|||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
border-bottom: 1px solid #333;
|
border-bottom: 1px solid #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navigation li.perfect-match {
|
||||||
|
border: 5px solid orange;
|
||||||
|
}
|
||||||
|
|
||||||
.navigation li.item a {
|
.navigation li.item a {
|
||||||
color: #bbb;
|
color: #bbb;
|
||||||
}
|
}
|
||||||
@@ -158,6 +172,12 @@ li {
|
|||||||
color: #1F6B75;
|
color: #1F6B75;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.navigation li.item .modulelink {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0.75em;
|
||||||
|
padding-left: 5px;
|
||||||
|
top: -5px;
|
||||||
|
}
|
||||||
.navigation li.item ul > li {
|
.navigation li.item ul > li {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
@@ -165,6 +185,7 @@ li {
|
|||||||
}
|
}
|
||||||
.navigation li.item .itemMembers {
|
.navigation li.item .itemMembers {
|
||||||
display: none;
|
display: none;
|
||||||
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
.main {
|
.main {
|
||||||
padding: 20px 20px;
|
padding: 20px 20px;
|
||||||
|
|||||||
81
config/jsdoc/api/template/static/styles/site.css
Normal file
81
config/jsdoc/api/template/static/styles/site.css
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/* Carbon adds (see https://sell.buysellads.com) */
|
||||||
|
|
||||||
|
#ad {
|
||||||
|
margin-left: 1em;
|
||||||
|
float: right;
|
||||||
|
width: 330px;
|
||||||
|
min-height: 125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads {
|
||||||
|
font-family: "Quattrocento Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads a:hover {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads span {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads .carbon-wrap {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carbon-img {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carbon-img img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carbon-text {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 10px;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carbon-poweredby {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 10px;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .5px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads a.carbon-poweredby {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear the float after the advertisement. */
|
||||||
|
|
||||||
|
.container-overview {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.source {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.content {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<?js= self.partial('source.tmpl', doc) ?>
|
<?js= self.partial('source.tmpl', doc) ?>
|
||||||
<?js } else { ?>
|
<?js } else { ?>
|
||||||
|
|
||||||
<section>
|
<section class="content">
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h2><?js if (doc.ancestors && doc.ancestors.length) { ?>
|
<h2><?js if (doc.ancestors && doc.ancestors.length) { ?>
|
||||||
@@ -41,6 +41,9 @@
|
|||||||
<pre class="prettyprint source"><code>import <?js= doc.name ?> from '<?js= importPath ?>';</code></pre>
|
<pre class="prettyprint source"><code>import <?js= doc.name ?> from '<?js= importPath ?>';</code></pre>
|
||||||
<?js } ?>
|
<?js } ?>
|
||||||
<?js } ?>
|
<?js } ?>
|
||||||
|
<div id="ad">
|
||||||
|
<script async type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7DV53U&placement=openlayersorg" id="_carbonads_js"></script>
|
||||||
|
</div>
|
||||||
<?js if (doc.classdesc) { ?>
|
<?js if (doc.classdesc) { ?>
|
||||||
<div class="class-description"><?js= doc.classdesc ?></div>
|
<div class="class-description"><?js= doc.classdesc ?></div>
|
||||||
<?js } ?>
|
<?js } ?>
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ var version = obj.packageInfo.version;
|
|||||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||||
<link type="text/css" rel="stylesheet" href="styles/bootstrap.min.css">
|
<link type="text/css" rel="stylesheet" href="styles/bootstrap.min.css">
|
||||||
<link type="text/css" rel="stylesheet" href="styles/jaguar.css">
|
<link type="text/css" rel="stylesheet" href="styles/jaguar.css">
|
||||||
|
<link type="text/css" rel="stylesheet" href="styles/site.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,16 @@ function toShortName(name) {
|
|||||||
</div>
|
</div>
|
||||||
<ul class="list">
|
<ul class="list">
|
||||||
<?js
|
<?js
|
||||||
let navbuilder;
|
this.nav.forEach(function (item) {
|
||||||
this.nav.forEach(navbuilder = function (item) {
|
|
||||||
?>
|
?>
|
||||||
<li class="item" data-name="<?js= item.longname ?>">
|
<li class="item" data-name="<?js= item.longname ?>" data-shortname="<?js= item.name.toLowerCase() ?>">
|
||||||
<span class="title">
|
<span class="title">
|
||||||
<?js= self.linkto(item.longname, item.type === 'module' ? item.longname.replace('module:', '') : item.name) ?>
|
<?js if (item.type === 'module') { ?>
|
||||||
|
<span class="glyphicon glyphicon-plus toggle"></span>
|
||||||
|
<?js } else if (item.type === 'class') { ?>
|
||||||
|
<span class="glyphicon glyphicon-chevron-right toggle"></span>
|
||||||
|
<?js } ?>
|
||||||
|
<?js= self.linkto(item.longname, item.prettyname) ?>
|
||||||
<?js if (item.type === 'namespace' &&
|
<?js if (item.type === 'namespace' &&
|
||||||
(item.members.length + item.typedefs.length + item.methods.length +
|
(item.members.length + item.typedefs.length + item.methods.length +
|
||||||
item.events.length > 0)) { ?>
|
item.events.length > 0)) { ?>
|
||||||
@@ -23,18 +27,6 @@ function toShortName(name) {
|
|||||||
</span>
|
</span>
|
||||||
<ul class="members itemMembers">
|
<ul class="members itemMembers">
|
||||||
<?js
|
<?js
|
||||||
if (item.classes.length) {
|
|
||||||
?>
|
|
||||||
<span class="subtitle">Classes</span>
|
|
||||||
<?js
|
|
||||||
item.classes.forEach(function (v) {
|
|
||||||
navbuilder(v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</ul>
|
|
||||||
<ul class="members itemMembers">
|
|
||||||
<?js
|
|
||||||
if (item.members.length) {
|
if (item.members.length) {
|
||||||
?>
|
?>
|
||||||
<span class="subtitle">Members</span>
|
<span class="subtitle">Members</span>
|
||||||
|
|||||||
@@ -16,9 +16,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* determine if we need extra columns, "attributes" and "default" */
|
/* determine if we need extra "attributes" column */
|
||||||
params.hasAttributes = false;
|
params.hasAttributes = false;
|
||||||
params.hasDefault = false;
|
|
||||||
params.hasName = false;
|
params.hasName = false;
|
||||||
|
|
||||||
var colspan = 2;
|
var colspan = 2;
|
||||||
@@ -40,10 +39,6 @@
|
|||||||
params.hasName = true;
|
params.hasName = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof param.defaultvalue !== 'undefined') {
|
|
||||||
++colspan;
|
|
||||||
params.hasDefault = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -56,10 +51,6 @@
|
|||||||
|
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
|
|
||||||
<?js if (params.hasDefault) {?>
|
|
||||||
<th>Default</th>
|
|
||||||
<?js } ?>
|
|
||||||
|
|
||||||
<th class="last">Description</th>
|
<th class="last">Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -80,16 +71,12 @@
|
|||||||
<td class="type">
|
<td class="type">
|
||||||
<?js if (param.type && param.type.names) {?>
|
<?js if (param.type && param.type.names) {?>
|
||||||
<?js= self.partial('type.tmpl', param.type.names) ?>
|
<?js= self.partial('type.tmpl', param.type.names) ?>
|
||||||
|
<?js if (typeof param.defaultvalue !== 'undefined') { ?>
|
||||||
|
(defaults to <?js= self.htmlsafe(param.defaultvalue) ?>)
|
||||||
|
<?js } ?>
|
||||||
<?js } ?>
|
<?js } ?>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<?js if (params.hasDefault) {?>
|
|
||||||
<td class="default">
|
|
||||||
<?js if (typeof param.defaultvalue !== 'undefined') { ?>
|
|
||||||
<?js= self.htmlsafe(param.defaultvalue) ?>
|
|
||||||
<?js } ?>
|
|
||||||
</td>
|
|
||||||
<?js } ?>
|
|
||||||
<?js } ?>
|
<?js } ?>
|
||||||
|
|
||||||
<td<?js= (param.subparams ? ' colspan=' + colspan : ' ') ?> class="description last">
|
<td<?js= (param.subparams ? ' colspan=' + colspan : ' ') ?> class="description last">
|
||||||
|
|||||||
@@ -241,10 +241,6 @@ Support for the `OES_element_index_uint` WebGL extension is mandatory for WebGL
|
|||||||
|
|
||||||
Layer opacity must be a number.
|
Layer opacity must be a number.
|
||||||
|
|
||||||
### 65
|
|
||||||
|
|
||||||
A symbol literal representation must be defined on the style supplied to a `WebGLPointsLayer` instance.
|
|
||||||
|
|
||||||
### 66
|
### 66
|
||||||
|
|
||||||
`forEachFeatureAtCoordinate` cannot be used on a WebGL layer if the hit detection logic has not been enabled.
|
`forEachFeatureAtCoordinate` cannot be used on a WebGL layer if the hit detection logic has not been enabled.
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ layout: example.html
|
|||||||
title: Filtering features with WebGL
|
title: Filtering features with WebGL
|
||||||
shortdesc: Using WebGL to filter large quantities of features
|
shortdesc: Using WebGL to filter large quantities of features
|
||||||
docs: >
|
docs: >
|
||||||
This example shows how to use `ol/renderer/webgl/PointsLayer` to dynamically filter a large amount
|
This example shows how to use `ol/layer/WebGLPoints` with a literal style to dynamically filter a large amount
|
||||||
of point geometries. The above map is based on a dataset from the NASA containing 45k recorded meteorite
|
of point geometries. The above map is based on a dataset from the NASA containing 45k recorded meteorite
|
||||||
landing sites. Each meteorite is marked by a circle on the map (the bigger the circle, the heavier
|
landing sites. Each meteorite is marked by a circle on the map (the bigger the circle, the heavier
|
||||||
the object). A pulse effect has been added, which is slightly offset by the year of the impact.
|
the object). A pulse effect has been added, which is slightly offset by the year of the impact.
|
||||||
|
|
||||||
Adjusting the sliders causes the objects outside of the date range to be filtered out of the map. This is done using
|
Adjusting the sliders causes the objects outside of the date range to be filtered out of the map. This is done
|
||||||
a custom fragment shader on the layer renderer, and by using the `v_opacity` attribute of the rendered objects
|
by mutating the variables in the `style` object provided to the WebGL layer. Also note that the last snippet
|
||||||
to store the year of impact.
|
of code is necessary to make sure the map refreshes itself every frame.
|
||||||
|
|
||||||
tags: "webgl, icon, sprite, filter, feature"
|
tags: "webgl, icon, sprite, filter, feature"
|
||||||
experimental: true
|
experimental: true
|
||||||
|
|||||||
@@ -3,133 +3,89 @@ import View from '../src/ol/View.js';
|
|||||||
import TileLayer from '../src/ol/layer/Tile.js';
|
import TileLayer from '../src/ol/layer/Tile.js';
|
||||||
import Feature from '../src/ol/Feature.js';
|
import Feature from '../src/ol/Feature.js';
|
||||||
import Point from '../src/ol/geom/Point.js';
|
import Point from '../src/ol/geom/Point.js';
|
||||||
import VectorLayer from '../src/ol/layer/Vector.js';
|
|
||||||
import {Vector} from '../src/ol/source.js';
|
import {Vector} from '../src/ol/source.js';
|
||||||
import {fromLonLat} from '../src/ol/proj.js';
|
import {fromLonLat} from '../src/ol/proj.js';
|
||||||
import WebGLPointsLayerRenderer from '../src/ol/renderer/webgl/PointsLayer.js';
|
|
||||||
import {clamp} from '../src/ol/math.js';
|
|
||||||
import Stamen from '../src/ol/source/Stamen.js';
|
import Stamen from '../src/ol/source/Stamen.js';
|
||||||
import {formatColor} from '../src/ol/webgl/ShaderBuilder.js';
|
import WebGLPointsLayer from '../src/ol/layer/WebGLPoints.js';
|
||||||
|
|
||||||
const vectorSource = new Vector({
|
const vectorSource = new Vector({
|
||||||
attributions: 'NASA'
|
attributions: 'NASA'
|
||||||
});
|
});
|
||||||
|
|
||||||
const oldColor = [180, 140, 140];
|
const oldColor = 'rgba(242,56,22,0.61)';
|
||||||
const newColor = [255, 80, 80];
|
const newColor = '#ffe52c';
|
||||||
|
const period = 12; // animation period in seconds
|
||||||
|
const animRatio =
|
||||||
|
['^',
|
||||||
|
['/',
|
||||||
|
['%',
|
||||||
|
['+',
|
||||||
|
['time'],
|
||||||
|
[
|
||||||
|
'interpolate',
|
||||||
|
['linear'],
|
||||||
|
['get', 'year'],
|
||||||
|
1850, 0,
|
||||||
|
2015, period
|
||||||
|
]
|
||||||
|
],
|
||||||
|
period
|
||||||
|
],
|
||||||
|
period
|
||||||
|
],
|
||||||
|
0.5
|
||||||
|
];
|
||||||
|
|
||||||
const startTime = Date.now() * 0.001;
|
const style = {
|
||||||
|
variables: {
|
||||||
|
minYear: 1850,
|
||||||
|
maxYear: 2015
|
||||||
|
},
|
||||||
|
filter: ['between', ['get', 'year'], ['var', 'minYear'], ['var', 'maxYear']],
|
||||||
|
symbol: {
|
||||||
|
symbolType: 'circle',
|
||||||
|
size: ['*',
|
||||||
|
['interpolate', ['linear'], ['get', 'mass'], 0, 8, 200000, 26],
|
||||||
|
['-', 1.75, ['*', animRatio, 0.75]]
|
||||||
|
],
|
||||||
|
color: ['interpolate',
|
||||||
|
['linear'],
|
||||||
|
animRatio,
|
||||||
|
0, newColor,
|
||||||
|
1, oldColor
|
||||||
|
],
|
||||||
|
opacity: ['-', 1.0, ['*', animRatio, 0.75]]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// hanle input values & events
|
// handle input values & events
|
||||||
const minYearInput = document.getElementById('min-year');
|
const minYearInput = document.getElementById('min-year');
|
||||||
const maxYearInput = document.getElementById('max-year');
|
const maxYearInput = document.getElementById('max-year');
|
||||||
|
|
||||||
|
function updateMinYear() {
|
||||||
|
style.variables.minYear = parseInt(minYearInput.value);
|
||||||
|
updateStatusText();
|
||||||
|
}
|
||||||
|
function updateMaxYear() {
|
||||||
|
style.variables.maxYear = parseInt(maxYearInput.value);
|
||||||
|
updateStatusText();
|
||||||
|
}
|
||||||
function updateStatusText() {
|
function updateStatusText() {
|
||||||
const div = document.getElementById('status');
|
const div = document.getElementById('status');
|
||||||
div.querySelector('span.min-year').textContent = minYearInput.value;
|
div.querySelector('span.min-year').textContent = minYearInput.value;
|
||||||
div.querySelector('span.max-year').textContent = maxYearInput.value;
|
div.querySelector('span.max-year').textContent = maxYearInput.value;
|
||||||
}
|
}
|
||||||
minYearInput.addEventListener('input', updateStatusText);
|
|
||||||
minYearInput.addEventListener('change', updateStatusText);
|
minYearInput.addEventListener('input', updateMinYear);
|
||||||
maxYearInput.addEventListener('input', updateStatusText);
|
minYearInput.addEventListener('change', updateMinYear);
|
||||||
maxYearInput.addEventListener('change', updateStatusText);
|
maxYearInput.addEventListener('input', updateMaxYear);
|
||||||
|
maxYearInput.addEventListener('change', updateMaxYear);
|
||||||
updateStatusText();
|
updateStatusText();
|
||||||
|
|
||||||
class WebglPointsLayer extends VectorLayer {
|
// load data
|
||||||
createRenderer() {
|
const client = new XMLHttpRequest();
|
||||||
return new WebGLPointsLayerRenderer(this, {
|
client.open('GET', 'data/csv/meteorite_landings.csv');
|
||||||
attributes: [
|
client.onload = function() {
|
||||||
{
|
|
||||||
name: 'size',
|
|
||||||
callback: function(feature) {
|
|
||||||
return 18 * clamp(feature.get('mass') / 200000, 0, 1) + 8;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'year',
|
|
||||||
callback: function(feature) {
|
|
||||||
return feature.get('year');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
vertexShader: [
|
|
||||||
'precision mediump float;',
|
|
||||||
|
|
||||||
'uniform mat4 u_projectionMatrix;',
|
|
||||||
'uniform mat4 u_offsetScaleMatrix;',
|
|
||||||
'uniform mat4 u_offsetRotateMatrix;',
|
|
||||||
'attribute vec2 a_position;',
|
|
||||||
'attribute float a_index;',
|
|
||||||
'attribute float a_size;',
|
|
||||||
'attribute float a_year;',
|
|
||||||
'varying vec2 v_texCoord;',
|
|
||||||
'varying float v_year;',
|
|
||||||
|
|
||||||
'void main(void) {',
|
|
||||||
' mat4 offsetMatrix = u_offsetScaleMatrix;',
|
|
||||||
' float offsetX = a_index == 0.0 || a_index == 3.0 ? -a_size / 2.0 : a_size / 2.0;',
|
|
||||||
' float offsetY = a_index == 0.0 || a_index == 1.0 ? -a_size / 2.0 : a_size / 2.0;',
|
|
||||||
' vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);',
|
|
||||||
' gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;',
|
|
||||||
' float u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;',
|
|
||||||
' float v = a_index == 0.0 || a_index == 1.0 ? 0.0 : 1.0;',
|
|
||||||
' v_texCoord = vec2(u, v);',
|
|
||||||
' v_year = a_year;',
|
|
||||||
'}'
|
|
||||||
].join(' '),
|
|
||||||
fragmentShader: [
|
|
||||||
'precision mediump float;',
|
|
||||||
|
|
||||||
'uniform float u_time;',
|
|
||||||
'uniform float u_minYear;',
|
|
||||||
'uniform float u_maxYear;',
|
|
||||||
'varying vec2 v_texCoord;',
|
|
||||||
'varying float v_year;',
|
|
||||||
|
|
||||||
'void main(void) {',
|
|
||||||
|
|
||||||
// filter out pixels if the year is outside of the given range
|
|
||||||
' if (v_year < u_minYear || v_year > u_maxYear) {',
|
|
||||||
' discard;',
|
|
||||||
' }',
|
|
||||||
|
|
||||||
' vec2 texCoord = v_texCoord * 2.0 - vec2(1.0, 1.0);',
|
|
||||||
' float sqRadius = texCoord.x * texCoord.x + texCoord.y * texCoord.y;',
|
|
||||||
' float value = 2.0 * (1.0 - sqRadius);',
|
|
||||||
' float alpha = smoothstep(0.0, 1.0, value);',
|
|
||||||
|
|
||||||
// color is interpolated based on year
|
|
||||||
' float ratio = clamp((v_year - 1800.0) / (2013.0 - 1800.0), 0.0, 1.1);',
|
|
||||||
' vec3 color = mix(vec3(' + formatColor(oldColor) + '),',
|
|
||||||
' vec3(' + formatColor(newColor) + '), ratio);',
|
|
||||||
|
|
||||||
' float period = 8.0;',
|
|
||||||
' color.g *= 2.0 * (1.0 - sqrt(mod(u_time + v_year * 0.025, period) / period));',
|
|
||||||
|
|
||||||
' gl_FragColor = vec4(color, 1.0);',
|
|
||||||
' gl_FragColor.a *= alpha;',
|
|
||||||
' gl_FragColor.rgb *= gl_FragColor.a;',
|
|
||||||
'}'
|
|
||||||
].join(' '),
|
|
||||||
uniforms: {
|
|
||||||
u_time: function() {
|
|
||||||
return Date.now() * 0.001 - startTime;
|
|
||||||
},
|
|
||||||
u_minYear: function() {
|
|
||||||
return parseInt(minYearInput.value);
|
|
||||||
},
|
|
||||||
u_maxYear: function() {
|
|
||||||
return parseInt(maxYearInput.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function loadData() {
|
|
||||||
const client = new XMLHttpRequest();
|
|
||||||
client.open('GET', 'data/csv/meteorite_landings.csv');
|
|
||||||
client.onload = function() {
|
|
||||||
const csv = client.responseText;
|
const csv = client.responseText;
|
||||||
const features = [];
|
const features = [];
|
||||||
|
|
||||||
@@ -154,11 +110,8 @@ function loadData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vectorSource.addFeatures(features);
|
vectorSource.addFeatures(features);
|
||||||
};
|
};
|
||||||
client.send();
|
client.send();
|
||||||
}
|
|
||||||
|
|
||||||
loadData();
|
|
||||||
|
|
||||||
const map = new Map({
|
const map = new Map({
|
||||||
layers: [
|
layers: [
|
||||||
@@ -167,8 +120,10 @@ const map = new Map({
|
|||||||
layer: 'toner'
|
layer: 'toner'
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
new WebglPointsLayer({
|
new WebGLPointsLayer({
|
||||||
source: vectorSource
|
style: style,
|
||||||
|
source: vectorSource,
|
||||||
|
disableHitDetection: true
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
target: document.getElementById('map'),
|
target: document.getElementById('map'),
|
||||||
|
|||||||
@@ -16,16 +16,15 @@ const vector = new HeatmapLayer({
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
blur: parseInt(blur.value, 10),
|
blur: parseInt(blur.value, 10),
|
||||||
radius: parseInt(radius.value, 10)
|
radius: parseInt(radius.value, 10),
|
||||||
});
|
weight: function(feature) {
|
||||||
|
|
||||||
vector.getSource().on('addfeature', function(event) {
|
|
||||||
// 2012_Earthquakes_Mag5.kml stores the magnitude of each earthquake in a
|
// 2012_Earthquakes_Mag5.kml stores the magnitude of each earthquake in a
|
||||||
// standards-violating <magnitude> tag in each Placemark. We extract it from
|
// standards-violating <magnitude> tag in each Placemark. We extract it from
|
||||||
// the Placemark's name instead.
|
// the Placemark's name instead.
|
||||||
const name = event.feature.get('name');
|
const name = feature.get('name');
|
||||||
const magnitude = parseFloat(name.substr(2));
|
const magnitude = parseFloat(name.substr(2));
|
||||||
event.feature.set('weight', magnitude - 5);
|
return magnitude - 5;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const raster = new TileLayer({
|
const raster = new TileLayer({
|
||||||
@@ -34,7 +33,7 @@ const raster = new TileLayer({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const map = new Map({
|
new Map({
|
||||||
layers: [raster, vector],
|
layers: [raster, vector],
|
||||||
target: 'map',
|
target: 'map',
|
||||||
view: new View({
|
view: new View({
|
||||||
|
|||||||
15
examples/hitdetect-vector.html
Normal file
15
examples/hitdetect-vector.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: Vector Layer Hit Detection
|
||||||
|
shortdesc: Example of hit detection on a countries vector layer with country information.
|
||||||
|
docs: >
|
||||||
|
The countries are loaded from a GeoJSON file. Information about countries is
|
||||||
|
on hover and click is retrieved using the layer's `getFeatures()` method. For
|
||||||
|
vector layers, this function resolves with an array of only the topmost
|
||||||
|
feature. It uses a very efficient hit detection algorithm, at the cost of
|
||||||
|
accuracy. For pixel exact hit detection, when performance is not a concern,
|
||||||
|
use the map's `getFeaturesAtPixel()` or `forEachFeatureAtPixel()` methods.
|
||||||
|
tags: "vector, geojson, click, hover, hit detection"
|
||||||
|
---
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
<div id="info"> </div>
|
||||||
113
examples/hitdetect-vector.js
Normal file
113
examples/hitdetect-vector.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import Map from '../src/ol/Map.js';
|
||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import GeoJSON from '../src/ol/format/GeoJSON.js';
|
||||||
|
import VectorLayer from '../src/ol/layer/Vector.js';
|
||||||
|
import VectorSource from '../src/ol/source/Vector.js';
|
||||||
|
import {Fill, Stroke, Style, Text} from '../src/ol/style.js';
|
||||||
|
|
||||||
|
|
||||||
|
const style = new Style({
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 0.6)'
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#319FD3',
|
||||||
|
width: 1
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
font: '12px Calibri,sans-serif',
|
||||||
|
fill: new Fill({
|
||||||
|
color: '#000'
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#fff',
|
||||||
|
width: 3
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const vectorLayer = new VectorLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
url: 'data/geojson/countries.geojson',
|
||||||
|
format: new GeoJSON()
|
||||||
|
}),
|
||||||
|
style: function(feature) {
|
||||||
|
style.getText().setText(feature.get('name'));
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
layers: [vectorLayer],
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 1
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const highlightStyle = new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#f00',
|
||||||
|
width: 1
|
||||||
|
}),
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255,0,0,0.1)'
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
font: '12px Calibri,sans-serif',
|
||||||
|
fill: new Fill({
|
||||||
|
color: '#000'
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#f00',
|
||||||
|
width: 3
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const featureOverlay = new VectorLayer({
|
||||||
|
source: new VectorSource(),
|
||||||
|
map: map,
|
||||||
|
style: function(feature) {
|
||||||
|
highlightStyle.getText().setText(feature.get('name'));
|
||||||
|
return highlightStyle;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let highlight;
|
||||||
|
const displayFeatureInfo = function(pixel) {
|
||||||
|
|
||||||
|
vectorLayer.getFeatures(pixel).then(function(features) {
|
||||||
|
const feature = features.length ? features[0] : undefined;
|
||||||
|
const info = document.getElementById('info');
|
||||||
|
if (features.length) {
|
||||||
|
info.innerHTML = feature.getId() + ': ' + feature.get('name');
|
||||||
|
} else {
|
||||||
|
info.innerHTML = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feature !== highlight) {
|
||||||
|
if (highlight) {
|
||||||
|
featureOverlay.getSource().removeFeature(highlight);
|
||||||
|
}
|
||||||
|
if (feature) {
|
||||||
|
featureOverlay.getSource().addFeature(feature);
|
||||||
|
}
|
||||||
|
highlight = feature;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
map.on('pointermove', function(evt) {
|
||||||
|
if (evt.dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pixel = map.getEventPixel(evt.originalEvent);
|
||||||
|
displayFeatureInfo(pixel);
|
||||||
|
});
|
||||||
|
|
||||||
|
map.on('click', function(evt) {
|
||||||
|
displayFeatureInfo(evt.pixel);
|
||||||
|
});
|
||||||
@@ -3,13 +3,13 @@ layout: example.html
|
|||||||
title: Icon Sprites with WebGL
|
title: Icon Sprites with WebGL
|
||||||
shortdesc: Rendering many icons with WebGL
|
shortdesc: Rendering many icons with WebGL
|
||||||
docs: >
|
docs: >
|
||||||
This example shows how to use `ol/renderer/webgl/PointsLayer` to render
|
This example shows how to use `ol/layer/WebGLPoints` to render
|
||||||
a very large amount of sprites. The above map is based on a dataset from the National UFO Reporting Center: each
|
a very large amount of sprites. The above map is based on a dataset from the National UFO Reporting Center: each
|
||||||
icon marks a UFO sighting according to its reported shape (disk, light, fireball...). The older the sighting, the redder
|
icon marks a UFO sighting according to its reported shape (disk, light, fireball...). The older the sighting, the redder
|
||||||
the icon.
|
the icon.
|
||||||
|
|
||||||
A very simple sprite atlas is used in the form of a PNG file containing all icons on a grid. Then, the `texCoordCallback`
|
A very simple sprite atlas is used in the form of a PNG file containing all icons on a grid. Then, the `style` object
|
||||||
option of the `ol/renderer/webgl/PointsLayer` constructor is used to specify which sprite to use according to the sighting shape.
|
given to the `ol/layer/WebGLPoints` constructor is used to specify which sprite to use according to the sighting shape.
|
||||||
|
|
||||||
The dataset contains around 80k points and can be found here: https://www.kaggle.com/NUFORC/ufo-sightings
|
The dataset contains around 80k points and can be found here: https://www.kaggle.com/NUFORC/ufo-sightings
|
||||||
tags: "webgl, icon, sprite, point, ufo"
|
tags: "webgl, icon, sprite, point, ufo"
|
||||||
@@ -20,3 +20,7 @@ cloak:
|
|||||||
---
|
---
|
||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
<div>Current sighting: <span id="info"></span></div>
|
<div>Current sighting: <span id="info"></span></div>
|
||||||
|
<div>
|
||||||
|
Filter by UFO shape:
|
||||||
|
<select id="shape-filter"></select>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -4,179 +4,103 @@ import TileLayer from '../src/ol/layer/Tile.js';
|
|||||||
import TileJSON from '../src/ol/source/TileJSON.js';
|
import TileJSON from '../src/ol/source/TileJSON.js';
|
||||||
import Feature from '../src/ol/Feature.js';
|
import Feature from '../src/ol/Feature.js';
|
||||||
import Point from '../src/ol/geom/Point.js';
|
import Point from '../src/ol/geom/Point.js';
|
||||||
import VectorLayer from '../src/ol/layer/Vector.js';
|
|
||||||
import {Vector} from '../src/ol/source.js';
|
import {Vector} from '../src/ol/source.js';
|
||||||
import {fromLonLat} from '../src/ol/proj.js';
|
import {fromLonLat} from '../src/ol/proj.js';
|
||||||
import WebGLPointsLayerRenderer from '../src/ol/renderer/webgl/PointsLayer.js';
|
import WebGLPointsLayer from '../src/ol/layer/WebGLPoints.js';
|
||||||
import {formatColor, formatNumber} from '../src/ol/webgl/ShaderBuilder.js';
|
|
||||||
|
|
||||||
const key = 'pk.eyJ1IjoidHNjaGF1YiIsImEiOiJjaW5zYW5lNHkxMTNmdWttM3JyOHZtMmNtIn0.CDIBD8H-G2Gf-cPkIuWtRg';
|
const key = 'pk.eyJ1IjoidHNjaGF1YiIsImEiOiJjaW5zYW5lNHkxMTNmdWttM3JyOHZtMmNtIn0.CDIBD8H-G2Gf-cPkIuWtRg';
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new TileJSON({
|
||||||
|
url: 'https://api.tiles.mapbox.com/v4/mapbox.world-dark.json?secure&access_token=' + key,
|
||||||
|
crossOrigin: 'anonymous'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
],
|
||||||
|
target: document.getElementById('map'),
|
||||||
|
view: new View({
|
||||||
|
center: [0, 4000000],
|
||||||
|
zoom: 2
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
const vectorSource = new Vector({
|
const vectorSource = new Vector({
|
||||||
features: [],
|
features: [],
|
||||||
attributions: 'National UFO Reporting Center'
|
attributions: 'National UFO Reporting Center'
|
||||||
});
|
});
|
||||||
|
|
||||||
const texture = new Image();
|
|
||||||
texture.src = 'data/ufo_shapes.png';
|
|
||||||
|
|
||||||
// This describes the content of the associated sprite sheet
|
|
||||||
// coords are u0, v0 for a given shape (all icons have a size of 0.25 x 0.5)
|
|
||||||
const shapeTextureCoords = {
|
|
||||||
'light': [0, 0],
|
|
||||||
'sphere': [0.25, 0],
|
|
||||||
'circle': [0.25, 0],
|
|
||||||
'disc': [0.5, 0],
|
|
||||||
'oval': [0.5, 0],
|
|
||||||
'triangle': [0.75, 0],
|
|
||||||
'fireball': [0, 0.5],
|
|
||||||
'default': [0.75, 0.5]
|
|
||||||
};
|
|
||||||
|
|
||||||
const oldColor = [255, 160, 110];
|
const oldColor = [255, 160, 110];
|
||||||
const newColor = [180, 255, 200];
|
const newColor = [180, 255, 200];
|
||||||
const size = 16;
|
const size = 16;
|
||||||
|
|
||||||
class WebglPointsLayer extends VectorLayer {
|
const style = {
|
||||||
createRenderer() {
|
variables: {
|
||||||
return new WebGLPointsLayerRenderer(this, {
|
filterShape: 'all'
|
||||||
attributes: [
|
|
||||||
{
|
|
||||||
name: 'year',
|
|
||||||
callback: function(feature) {
|
|
||||||
return feature.get('year');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
filter: [
|
||||||
name: 'texCoordU',
|
'case',
|
||||||
callback: function(feature) {
|
['!=', ['var', 'filterShape'], 'all'],
|
||||||
let coords = shapeTextureCoords[feature.get('shape')];
|
['==', ['get', 'shape'], ['var', 'filterShape']],
|
||||||
if (!coords) {
|
true
|
||||||
coords = shapeTextureCoords['default'];
|
|
||||||
}
|
|
||||||
return coords[0];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'texCoordV',
|
|
||||||
callback: function(feature) {
|
|
||||||
let coords = shapeTextureCoords[feature.get('shape')];
|
|
||||||
if (!coords) {
|
|
||||||
coords = shapeTextureCoords['default'];
|
|
||||||
}
|
|
||||||
return coords[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
uniforms: {
|
symbol: {
|
||||||
u_texture: texture
|
symbolType: 'image',
|
||||||
},
|
src: 'data/ufo_shapes.png',
|
||||||
vertexShader: [
|
size: size,
|
||||||
'precision mediump float;',
|
color: [
|
||||||
|
'interpolate',
|
||||||
'uniform mat4 u_projectionMatrix;',
|
['linear'],
|
||||||
'uniform mat4 u_offsetScaleMatrix;',
|
['get', 'year'],
|
||||||
'uniform mat4 u_offsetRotateMatrix;',
|
1950, oldColor,
|
||||||
'attribute vec2 a_position;',
|
2013, newColor
|
||||||
'attribute float a_index;',
|
],
|
||||||
'attribute float a_year;',
|
rotateWithView: false,
|
||||||
'attribute float a_texCoordU;',
|
offset: [
|
||||||
'attribute float a_texCoordV;',
|
0,
|
||||||
'varying vec2 v_texCoord;',
|
9
|
||||||
'varying float v_year;',
|
],
|
||||||
|
textureCoord: [
|
||||||
'void main(void) {',
|
'match',
|
||||||
' mat4 offsetMatrix = u_offsetScaleMatrix;',
|
['get', 'shape'],
|
||||||
' float offsetX = a_index == 0.0 || a_index == 3.0 ? ',
|
'light', [0, 0, 0.25, 0.5],
|
||||||
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
'sphere', [0.25, 0, 0.5, 0.5],
|
||||||
' float offsetY = a_index == 0.0 || a_index == 1.0 ? ',
|
'circle', [0.25, 0, 0.5, 0.5],
|
||||||
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
'disc', [0.5, 0, 0.75, 0.5],
|
||||||
' vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);',
|
'oval', [0.5, 0, 0.75, 0.5],
|
||||||
' gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;',
|
'triangle', [0.75, 0, 1, 0.5],
|
||||||
' float u = a_index == 0.0 || a_index == 3.0 ? a_texCoordU : a_texCoordU + 0.25;',
|
'fireball', [0, 0.5, 0.25, 1],
|
||||||
' float v = a_index == 2.0 || a_index == 3.0 ? a_texCoordV : a_texCoordV + 0.5;',
|
[0.75, 0.5, 1, 1]
|
||||||
' v_texCoord = vec2(u, v);',
|
]
|
||||||
' v_year = a_year;',
|
|
||||||
'}'
|
|
||||||
].join(' '),
|
|
||||||
fragmentShader: [
|
|
||||||
'precision mediump float;',
|
|
||||||
|
|
||||||
'uniform float u_time;',
|
|
||||||
'uniform float u_minYear;',
|
|
||||||
'uniform float u_maxYear;',
|
|
||||||
'uniform sampler2D u_texture;',
|
|
||||||
'varying vec2 v_texCoord;',
|
|
||||||
'varying float v_year;',
|
|
||||||
|
|
||||||
'void main(void) {',
|
|
||||||
' vec4 textureColor = texture2D(u_texture, v_texCoord);',
|
|
||||||
' if (textureColor.a < 0.1) {',
|
|
||||||
' discard;',
|
|
||||||
' }',
|
|
||||||
|
|
||||||
// color is interpolated based on year
|
|
||||||
' float ratio = clamp((v_year - 1950.0) / (2013.0 - 1950.0), 0.0, 1.1);',
|
|
||||||
' vec3 color = mix(vec3(' + formatColor(oldColor) + '),',
|
|
||||||
' vec3(' + formatColor(newColor) + '), ratio);',
|
|
||||||
|
|
||||||
' gl_FragColor = vec4(color, 1.0) * textureColor;',
|
|
||||||
' gl_FragColor.rgb *= gl_FragColor.a;',
|
|
||||||
'}'
|
|
||||||
].join(' '),
|
|
||||||
hitVertexShader: [
|
|
||||||
'precision mediump float;',
|
|
||||||
|
|
||||||
'uniform mat4 u_projectionMatrix;',
|
|
||||||
'uniform mat4 u_offsetScaleMatrix;',
|
|
||||||
'uniform mat4 u_offsetRotateMatrix;',
|
|
||||||
'attribute vec2 a_position;',
|
|
||||||
'attribute float a_index;',
|
|
||||||
'attribute vec4 a_hitColor;',
|
|
||||||
'attribute float a_texCoordU;',
|
|
||||||
'attribute float a_texCoordV;',
|
|
||||||
'varying vec2 v_texCoord;',
|
|
||||||
'varying vec4 v_hitColor;',
|
|
||||||
|
|
||||||
'void main(void) {',
|
|
||||||
' mat4 offsetMatrix = u_offsetScaleMatrix;',
|
|
||||||
' float offsetX = a_index == 0.0 || a_index == 3.0 ? ',
|
|
||||||
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
|
||||||
' float offsetY = a_index == 0.0 || a_index == 1.0 ? ',
|
|
||||||
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
|
||||||
' vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);',
|
|
||||||
' gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;',
|
|
||||||
' float u = a_index == 0.0 || a_index == 3.0 ? a_texCoordU : a_texCoordU + 0.25;',
|
|
||||||
' float v = a_index == 2.0 || a_index == 3.0 ? a_texCoordV : a_texCoordV + 0.5;',
|
|
||||||
' v_texCoord = vec2(u, v);',
|
|
||||||
' v_hitColor = a_hitColor;',
|
|
||||||
'}'
|
|
||||||
].join(' '),
|
|
||||||
hitFragmentShader: [
|
|
||||||
'precision mediump float;',
|
|
||||||
|
|
||||||
'uniform sampler2D u_texture;',
|
|
||||||
'varying vec2 v_texCoord;',
|
|
||||||
'varying vec4 v_hitColor;',
|
|
||||||
|
|
||||||
'void main(void) {',
|
|
||||||
' vec4 textureColor = texture2D(u_texture, v_texCoord);',
|
|
||||||
' if (textureColor.a < 0.1) {',
|
|
||||||
' discard;',
|
|
||||||
' }',
|
|
||||||
|
|
||||||
' gl_FragColor = v_hitColor;',
|
|
||||||
'}'
|
|
||||||
].join(' ')
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// key is shape name, value is sightings count
|
||||||
|
const shapeTypes = {
|
||||||
|
all: 0
|
||||||
|
};
|
||||||
|
const shapeSelect = document.getElementById('shape-filter');
|
||||||
|
shapeSelect.addEventListener('input', function() {
|
||||||
|
style.variables.filterShape = shapeSelect.options[shapeSelect.selectedIndex].value;
|
||||||
|
map.render();
|
||||||
|
});
|
||||||
|
function fillShapeSelect() {
|
||||||
|
Object.keys(shapeTypes)
|
||||||
|
.sort(function(a, b) {
|
||||||
|
return shapeTypes[b] - shapeTypes[a];
|
||||||
|
})
|
||||||
|
.forEach(function(shape) {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.text = `${shape} (${shapeTypes[shape]} sightings)`;
|
||||||
|
option.value = shape;
|
||||||
|
shapeSelect.appendChild(option);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const client = new XMLHttpRequest();
|
||||||
function loadData() {
|
client.open('GET', 'data/csv/ufo_sighting_data.csv');
|
||||||
const client = new XMLHttpRequest();
|
client.onload = function() {
|
||||||
client.open('GET', 'data/csv/ufo_sighting_data.csv');
|
|
||||||
client.onload = function() {
|
|
||||||
const csv = client.responseText;
|
const csv = client.responseText;
|
||||||
const features = [];
|
const features = [];
|
||||||
|
|
||||||
@@ -194,39 +118,29 @@ function loadData() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shape = line[2];
|
||||||
|
shapeTypes[shape] = (shapeTypes[shape] ? shapeTypes[shape] : 0) + 1;
|
||||||
|
shapeTypes['all']++;
|
||||||
|
|
||||||
features.push(new Feature({
|
features.push(new Feature({
|
||||||
datetime: line[0],
|
datetime: line[0],
|
||||||
year: parseInt(/[0-9]{4}/.exec(line[0])[0]), // extract the year as int
|
year: parseInt(/[0-9]{4}/.exec(line[0])[0]), // extract the year as int
|
||||||
shape: line[2],
|
shape: shape,
|
||||||
duration: line[3],
|
duration: line[3],
|
||||||
geometry: new Point(coords)
|
geometry: new Point(coords)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
vectorSource.addFeatures(features);
|
vectorSource.addFeatures(features);
|
||||||
};
|
fillShapeSelect();
|
||||||
client.send();
|
};
|
||||||
}
|
client.send();
|
||||||
|
|
||||||
loadData();
|
map.addLayer(
|
||||||
|
new WebGLPointsLayer({
|
||||||
const map = new Map({
|
source: vectorSource,
|
||||||
layers: [
|
style: style
|
||||||
new TileLayer({
|
|
||||||
source: new TileJSON({
|
|
||||||
url: 'https://api.tiles.mapbox.com/v4/mapbox.world-dark.json?secure&access_token=' + key,
|
|
||||||
crossOrigin: 'anonymous'
|
|
||||||
})
|
})
|
||||||
}),
|
);
|
||||||
new WebglPointsLayer({
|
|
||||||
source: vectorSource
|
|
||||||
})
|
|
||||||
],
|
|
||||||
target: document.getElementById('map'),
|
|
||||||
view: new View({
|
|
||||||
center: [0, 4000000],
|
|
||||||
zoom: 2
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const info = document.getElementById('info');
|
const info = document.getElementById('info');
|
||||||
map.on('pointermove', function(evt) {
|
map.on('pointermove', function(evt) {
|
||||||
@@ -242,7 +156,3 @@ map.on('pointermove', function(evt) {
|
|||||||
info.innerText = 'On ' + datetime + ', lasted ' + duration + ' seconds and had a "' + shape + '" shape.';
|
info.innerText = 'On ' + datetime + ', lasted ' + duration + ' seconds and had a "' + shape + '" shape.';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
texture.addEventListener('load', function() {
|
|
||||||
map.render();
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -56,9 +56,8 @@ const featureOverlay = new VectorLayer({
|
|||||||
let highlight;
|
let highlight;
|
||||||
const displayFeatureInfo = function(pixel) {
|
const displayFeatureInfo = function(pixel) {
|
||||||
|
|
||||||
const feature = map.forEachFeatureAtPixel(pixel, function(feature) {
|
map.getLayers().item(0).getFeatures(pixel).then(function(features) {
|
||||||
return feature;
|
const feature = features.length > 0 ? features[0] : undefined;
|
||||||
});
|
|
||||||
|
|
||||||
const info = document.getElementById('info');
|
const info = document.getElementById('info');
|
||||||
if (feature) {
|
if (feature) {
|
||||||
@@ -76,15 +75,13 @@ const displayFeatureInfo = function(pixel) {
|
|||||||
}
|
}
|
||||||
highlight = feature;
|
highlight = feature;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
map.on('pointermove', function(evt) {
|
map.on('pointermove', function(evt) {
|
||||||
if (evt.dragging) {
|
if (!evt.dragging) {
|
||||||
return;
|
displayFeatureInfo(evt.pixel);
|
||||||
}
|
}
|
||||||
const pixel = map.getEventPixel(evt.originalEvent);
|
|
||||||
displayFeatureInfo(pixel);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
map.on('click', function(evt) {
|
map.on('click', function(evt) {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
---
|
---
|
||||||
layout: example-verbatim.html
|
layout: example-verbatim.html
|
||||||
title: Vector tiles created from a Mapbox Style object
|
title: Vector tiles created from a Mapbox Style object
|
||||||
shortdesc: Example of using ol-mapbox-style with tiles from tilehosting.com.
|
shortdesc: Example of using ol-mapbox-style with tiles from maptiler.com.
|
||||||
|
docs: >
|
||||||
|
Example of using `ol-mapbox-style` with tiles from maptiler.com.
|
||||||
|
**Note**: Make sure to get your own API key at https://www.maptiler.com/cloud/ when using this example. No map will be visible when the API key has expired.
|
||||||
tags: "vector tiles, mapbox style, ol-mapbox-style, maptiler"
|
tags: "vector tiles, mapbox style, ol-mapbox-style, maptiler"
|
||||||
cloak:
|
cloak:
|
||||||
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
|
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
|
||||||
|
|||||||
@@ -74,13 +74,9 @@ ol.inline>li {
|
|||||||
.ol-attribution.ol-logo-only,
|
.ol-attribution.ol-logo-only,
|
||||||
.ol-attribution.ol-uncollapsible {
|
.ol-attribution.ol-uncollapsible {
|
||||||
max-width: calc(100% - 3em);
|
max-width: calc(100% - 3em);
|
||||||
height: 1.5em;
|
|
||||||
}
|
}
|
||||||
.ol-attribution ul {
|
.ol-attribution ul {
|
||||||
font-size: 1rem;
|
font-size: 14px;
|
||||||
}
|
|
||||||
.ol-control button, .ol-attribution, .ol-scale-line-inner {
|
|
||||||
font-family: 'Lucida Grande',Verdana,Geneva,Lucida,Arial,Helvetica,sans-serif;
|
|
||||||
}
|
}
|
||||||
.iframe-info iframe {
|
.iframe-info iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
22
examples/select-features.html
Normal file
22
examples/select-features.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: Select Features
|
||||||
|
shortdesc: Example of using the Select interaction.
|
||||||
|
docs: >
|
||||||
|
Choose between <code>Single-click</code>, <code>Click</code>, <code>Hover</code> and <code>Alt+Click</code> as the event type for selection in the combobox below. When using <code>Single-click</code> or <code>Click</code> you can hold do <code>Shift</code> key to toggle the feature in the selection.</p>
|
||||||
|
<p>Note: when <code>Single-click</code> is used double-clicks won't select features. This in contrast to <code>Click</code>, where a double-click will both select the feature and zoom the map (because of the <code>DoubleClickZoom</code> interaction). Note that <code>Single-click</code> is less responsive than <code>Click</code> because of the delay it uses to detect double-clicks.</p>
|
||||||
|
<p>In this example, a listener is registered for the Select interaction's <code>select</code> event in order to update the selection status above.
|
||||||
|
tags: "select, vector"
|
||||||
|
---
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
<form class="form-inline">
|
||||||
|
<label>Action type </label>
|
||||||
|
<select id="type" class="form-control">
|
||||||
|
<option value="click" selected>Click</option>
|
||||||
|
<option value="singleclick">Single-click</option>
|
||||||
|
<option value="pointermove">Hover</option>
|
||||||
|
<option value="altclick">Alt+Click</option>
|
||||||
|
<option value="none">None</option>
|
||||||
|
</select>
|
||||||
|
<span id="status"> 0 selected features</span>
|
||||||
|
</form>
|
||||||
@@ -23,7 +23,7 @@ map.on('pointermove', showInfo);
|
|||||||
const info = document.getElementById('info');
|
const info = document.getElementById('info');
|
||||||
function showInfo(event) {
|
function showInfo(event) {
|
||||||
const features = map.getFeaturesAtPixel(event.pixel);
|
const features = map.getFeaturesAtPixel(event.pixel);
|
||||||
if (!features) {
|
if (features.length == 0) {
|
||||||
info.innerText = '';
|
info.innerText = '';
|
||||||
info.style.opacity = 0;
|
info.style.opacity = 0;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ const map = new Map({
|
|||||||
const selectElement = document.getElementById('type');
|
const selectElement = document.getElementById('type');
|
||||||
|
|
||||||
map.on('click', function(event) {
|
map.on('click', function(event) {
|
||||||
const features = map.getFeaturesAtPixel(event.pixel);
|
vtLayer.getFeatures(event.pixel).then(function(features) {
|
||||||
if (!features) {
|
if (!features.length) {
|
||||||
selection = {};
|
selection = {};
|
||||||
// force redraw of layer style
|
// force redraw of layer style
|
||||||
vtLayer.setStyle(vtLayer.getStyle());
|
vtLayer.setStyle(vtLayer.getStyle());
|
||||||
@@ -56,7 +56,6 @@ map.on('click', function(event) {
|
|||||||
if (!feature) {
|
if (!feature) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fid = feature.get(idProp);
|
const fid = feature.get(idProp);
|
||||||
|
|
||||||
if (selectElement.value === 'singleselect') {
|
if (selectElement.value === 'singleselect') {
|
||||||
@@ -67,4 +66,6 @@ map.on('click', function(event) {
|
|||||||
|
|
||||||
// force redraw of layer style
|
// force redraw of layer style
|
||||||
vtLayer.setStyle(vtLayer.getStyle());
|
vtLayer.setStyle(vtLayer.getStyle());
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
13
examples/vector-tiles-4326.html
Normal file
13
examples/vector-tiles-4326.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: Vector tiles in EPSG:4326
|
||||||
|
shortdesc: Example showing vector tiles in EPSG:4326 (styled using ol-mapbox-style)
|
||||||
|
docs: >
|
||||||
|
Example showing vector tiles in EPSG:4326 (styled using `ol-mapbox-style`) loaded from maptiler.com.
|
||||||
|
**Note**: Make sure to get your own API key at https://www.maptiler.com/cloud/ when using this example. No map will be visible when the API key has expired.
|
||||||
|
tags: "vector tiles, epsg4326, mapbox style, ol-mapbox-style, maptiler"
|
||||||
|
cloak:
|
||||||
|
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
|
||||||
|
value: Get your own API key at https://www.maptiler.com/cloud/
|
||||||
|
---
|
||||||
|
<div id="map" class="map" style="background:none;"></div>
|
||||||
50
examples/vector-tiles-4326.js
Normal file
50
examples/vector-tiles-4326.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import MVT from '../src/ol/format/MVT.js';
|
||||||
|
import VectorTileSource from '../src/ol/source/VectorTile.js';
|
||||||
|
import TileGrid from '../src/ol/tilegrid/TileGrid.js';
|
||||||
|
|
||||||
|
import olms from 'ol-mapbox-style';
|
||||||
|
import {defaultResolutions} from 'ol-mapbox-style/util.js';
|
||||||
|
|
||||||
|
const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
|
||||||
|
|
||||||
|
// Match the server resolutions
|
||||||
|
const maxResolution = 360 / 512;
|
||||||
|
defaultResolutions.length = 14;
|
||||||
|
for (let i = 0; i < 14; ++i) {
|
||||||
|
defaultResolutions[i] = maxResolution / Math.pow(2, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
olms('map', 'https://api.maptiler.com/maps/basic-4326/style.json?key=' + key).then(function(map) {
|
||||||
|
|
||||||
|
// Custom tile grid for the EPSG:4326 projection
|
||||||
|
const tileGrid = new TileGrid({
|
||||||
|
extent: [-180, -90, 180, 90],
|
||||||
|
tileSize: 512,
|
||||||
|
resolutions: defaultResolutions
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapboxStyle = map.get('mapbox-style');
|
||||||
|
|
||||||
|
// Replace the source with a EPSG:4326 projection source for each vector tile layer
|
||||||
|
map.getLayers().forEach(function(layer) {
|
||||||
|
const mapboxSource = layer.get('mapbox-source');
|
||||||
|
if (mapboxSource && mapboxStyle.sources[mapboxSource].type === 'vector') {
|
||||||
|
const source = layer.getSource();
|
||||||
|
layer.setSource(new VectorTileSource({
|
||||||
|
format: new MVT(),
|
||||||
|
projection: 'EPSG:4326',
|
||||||
|
urls: source.getUrls(),
|
||||||
|
tileGrid: tileGrid
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure the map with a view with EPSG:4326 projection
|
||||||
|
map.setView(new View({
|
||||||
|
projection: 'EPSG:4326',
|
||||||
|
zoom: mapboxStyle.zoom,
|
||||||
|
center: mapboxStyle.center
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
@@ -24,15 +24,15 @@ experimental: true
|
|||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
<label>Choose a predefined style from the list below or edit it as JSON manually.</label><br>
|
<label>Choose a predefined style from the list below or edit it as JSON manually.</label><br>
|
||||||
<select id="style-select">
|
<select id="style-select">
|
||||||
<option>Predefined styles</option>
|
|
||||||
<option value="icons">Icons</option>
|
<option value="icons">Icons</option>
|
||||||
<option value="triangles">Triangles, color related to population</option>
|
<option value="triangles">Triangles, color related to population</option>
|
||||||
|
<option value="triangles-latitude">Triangles, color related to latitude</option>
|
||||||
<option value="circles">Circles, size related to population</option>
|
<option value="circles">Circles, size related to population</option>
|
||||||
|
<option value="circles-zoom">Circles, size related to zoom</option>
|
||||||
</select>
|
</select>
|
||||||
<textarea style="width: 100%; height: 20rem; font-family: monospace; font-size: small;" id="style-editor"></textarea>
|
<textarea style="width: 100%; height: 20rem; font-family: monospace; font-size: small;" id="style-editor"></textarea>
|
||||||
<small id="style-valid" style="display: none; color: forestgreen">
|
<small>
|
||||||
✓ style is valid
|
<span id="style-valid" style="display: none; color: forestgreen">✓ style is valid</span>
|
||||||
</small>
|
<span id="style-invalid" style="display: none; color: grey">✗ <span>style not yet valid...</span></span>
|
||||||
<small id="style-invalid" style="display: none; color: grey">
|
|
||||||
✗ style not yet valid...
|
|
||||||
</small>
|
</small>
|
||||||
|
|||||||
@@ -27,27 +27,76 @@ const predefinedStyles = {
|
|||||||
symbolType: 'triangle',
|
symbolType: 'triangle',
|
||||||
size: 18,
|
size: 18,
|
||||||
color: [
|
color: [
|
||||||
['stretch', ['get', 'population'], 20000, 300000, 0.1, 1.0],
|
'interpolate',
|
||||||
['stretch', ['get', 'population'], 20000, 300000, 0.6, 0.3],
|
['linear'],
|
||||||
0.6,
|
['get', 'population'],
|
||||||
1.0
|
20000, '#5aca5b',
|
||||||
|
300000, '#ff6a19'
|
||||||
],
|
],
|
||||||
rotateWithView: true
|
rotateWithView: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'triangles-latitude': {
|
||||||
|
symbol: {
|
||||||
|
symbolType: 'triangle',
|
||||||
|
size: [
|
||||||
|
'interpolate',
|
||||||
|
['linear'],
|
||||||
|
['get', 'population'],
|
||||||
|
40000, 12,
|
||||||
|
2000000, 24
|
||||||
|
],
|
||||||
|
color: [
|
||||||
|
'interpolate',
|
||||||
|
['linear'],
|
||||||
|
['get', 'latitude'],
|
||||||
|
-60, '#ff14c3',
|
||||||
|
-20, '#ff621d',
|
||||||
|
20, '#ffed02',
|
||||||
|
60, '#00ff67'
|
||||||
|
],
|
||||||
|
offset: [0, 0],
|
||||||
|
opacity: 0.95
|
||||||
|
}
|
||||||
|
},
|
||||||
'circles': {
|
'circles': {
|
||||||
symbol: {
|
symbol: {
|
||||||
symbolType: 'circle',
|
symbolType: 'circle',
|
||||||
size: ['stretch', ['get', 'population'], 40000, 2000000, 8, 28],
|
size: [
|
||||||
|
'interpolate',
|
||||||
|
['linear'],
|
||||||
|
['get', 'population'],
|
||||||
|
40000, 8,
|
||||||
|
2000000, 28
|
||||||
|
],
|
||||||
color: '#006688',
|
color: '#006688',
|
||||||
rotateWithView: false,
|
rotateWithView: false,
|
||||||
offset: [0, 0],
|
offset: [0, 0],
|
||||||
opacity: ['stretch', ['get', 'population'], 40000, 2000000, 0.6, 0.92]
|
opacity: [
|
||||||
|
'interpolate',
|
||||||
|
['linear'],
|
||||||
|
['get', 'population'],
|
||||||
|
40000, 0.6,
|
||||||
|
2000000, 0.92
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'circles-zoom': {
|
||||||
|
symbol: {
|
||||||
|
symbolType: 'circle',
|
||||||
|
size: [
|
||||||
|
'interpolate',
|
||||||
|
['exponential', 2.5],
|
||||||
|
['zoom'],
|
||||||
|
2, 1,
|
||||||
|
14, 32
|
||||||
|
],
|
||||||
|
color: '#240572',
|
||||||
|
offset: [0, 0],
|
||||||
|
opacity: 0.95
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let literalStyle = predefinedStyles['circles'];
|
|
||||||
let pointsLayer;
|
|
||||||
|
|
||||||
const map = new Map({
|
const map = new Map({
|
||||||
layers: [
|
layers: [
|
||||||
@@ -62,45 +111,58 @@ const map = new Map({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const editor = document.getElementById('style-editor');
|
let literalStyle;
|
||||||
editor.value = JSON.stringify(literalStyle, null, 2);
|
let pointsLayer;
|
||||||
|
function refreshLayer(newStyle) {
|
||||||
function refreshLayer() {
|
const previousLayer = pointsLayer;
|
||||||
if (pointsLayer) {
|
|
||||||
map.removeLayer(pointsLayer);
|
|
||||||
}
|
|
||||||
pointsLayer = new WebGLPointsLayer({
|
pointsLayer = new WebGLPointsLayer({
|
||||||
source: vectorSource,
|
source: vectorSource,
|
||||||
style: literalStyle
|
style: newStyle,
|
||||||
|
disableHitDetection: true
|
||||||
});
|
});
|
||||||
map.addLayer(pointsLayer);
|
map.addLayer(pointsLayer);
|
||||||
|
|
||||||
|
if (previousLayer) {
|
||||||
|
map.removeLayer(previousLayer);
|
||||||
|
}
|
||||||
|
literalStyle = newStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStyleStatus(valid) {
|
const spanValid = document.getElementById('style-valid');
|
||||||
document.getElementById('style-valid').style.display = valid ? 'initial' : 'none';
|
const spanInvalid = document.getElementById('style-invalid');
|
||||||
document.getElementById('style-invalid').style.display = !valid ? 'initial' : 'none';
|
function setStyleStatus(errorMsg) {
|
||||||
|
const isError = typeof errorMsg === 'string';
|
||||||
|
spanValid.style.display = errorMsg === null ? 'initial' : 'none';
|
||||||
|
spanInvalid.firstElementChild.innerText = isError ? errorMsg : '';
|
||||||
|
spanInvalid.style.display = isError ? 'initial' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const editor = document.getElementById('style-editor');
|
||||||
editor.addEventListener('input', function() {
|
editor.addEventListener('input', function() {
|
||||||
const textStyle = editor.value;
|
const textStyle = editor.value;
|
||||||
if (JSON.stringify(JSON.parse(textStyle)) === JSON.stringify(literalStyle)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
literalStyle = JSON.parse(textStyle);
|
const newLiteralStyle = JSON.parse(textStyle);
|
||||||
refreshLayer();
|
if (JSON.stringify(newLiteralStyle) !== JSON.stringify(literalStyle)) {
|
||||||
setStyleStatus(true);
|
refreshLayer(newLiteralStyle);
|
||||||
|
}
|
||||||
|
setStyleStatus(null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setStyleStatus(false);
|
setStyleStatus(e.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
refreshLayer();
|
|
||||||
|
|
||||||
const select = document.getElementById('style-select');
|
const select = document.getElementById('style-select');
|
||||||
select.addEventListener('change', function() {
|
select.value = 'circles';
|
||||||
|
function onSelectChange() {
|
||||||
const style = select.value;
|
const style = select.value;
|
||||||
literalStyle = predefinedStyles[style];
|
const newLiteralStyle = predefinedStyles[style];
|
||||||
editor.value = JSON.stringify(literalStyle, null, 2);
|
editor.value = JSON.stringify(newLiteralStyle, null, 2);
|
||||||
refreshLayer();
|
try {
|
||||||
});
|
refreshLayer(newLiteralStyle);
|
||||||
|
setStyleStatus();
|
||||||
|
} catch (e) {
|
||||||
|
setStyleStatus(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onSelectChange();
|
||||||
|
select.addEventListener('change', onSelectChange);
|
||||||
|
|||||||
699
package-lock.json
generated
699
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ol",
|
"name": "ol",
|
||||||
"version": "6.0.1",
|
"version": "6.1.0",
|
||||||
"description": "OpenLayers mapping library",
|
"description": "OpenLayers mapping library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"map",
|
"map",
|
||||||
@@ -37,59 +37,59 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openlayers/pepjs": "^0.5.3",
|
"@openlayers/pepjs": "^0.5.3",
|
||||||
"pbf": "3.2.0",
|
"pbf": "3.2.1",
|
||||||
"pixelworks": "1.1.0",
|
"pixelworks": "1.1.0",
|
||||||
"rbush": "^3.0.0"
|
"rbush": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.4.0",
|
"@babel/core": "^7.6.4",
|
||||||
"@babel/preset-env": "^7.4.4",
|
"@babel/preset-env": "^7.4.4",
|
||||||
"@openlayers/eslint-plugin": "^4.0.0",
|
"@openlayers/eslint-plugin": "^4.0.0",
|
||||||
"@types/arcgis-rest-api": "^10.4.4",
|
"@types/arcgis-rest-api": "^10.4.4",
|
||||||
"@types/geojson": "^7946.0.7",
|
"@types/geojson": "^7946.0.7",
|
||||||
"@types/pbf": "^3.0.1",
|
"@types/pbf": "^3.0.2",
|
||||||
"@types/topojson-specification": "^1.0.1",
|
"@types/topojson-specification": "^1.0.1",
|
||||||
"babel-loader": "^8.0.5",
|
"babel-loader": "^8.0.5",
|
||||||
"buble": "^0.19.7",
|
"buble": "^0.19.7",
|
||||||
"buble-loader": "^0.5.1",
|
"buble-loader": "^0.5.1",
|
||||||
"chaikin-smooth": "^1.0.4",
|
"chaikin-smooth": "^1.0.4",
|
||||||
"clean-css-cli": "4.3.0",
|
"clean-css-cli": "4.3.0",
|
||||||
"copy-webpack-plugin": "^5.0.3",
|
"copy-webpack-plugin": "^5.0.4",
|
||||||
"coveralls": "3.0.6",
|
"coveralls": "3.0.7",
|
||||||
"eslint": "^6.0.0",
|
"eslint": "^6.0.0",
|
||||||
"eslint-config-openlayers": "^12.0.0",
|
"eslint-config-openlayers": "^12.0.0",
|
||||||
"expect.js": "0.3.1",
|
"expect.js": "0.3.1",
|
||||||
"front-matter": "^3.0.2",
|
"front-matter": "^3.0.2",
|
||||||
"fs-extra": "^8.0.0",
|
"fs-extra": "^8.0.0",
|
||||||
"glob": "^7.1.4",
|
"glob": "^7.1.5",
|
||||||
"globby": "^10.0.0",
|
"globby": "^10.0.0",
|
||||||
"handlebars": "4.4.0",
|
"handlebars": "4.4.5",
|
||||||
"html-to-image": "^0.1.0",
|
"html-to-image": "^0.1.0",
|
||||||
"istanbul": "0.4.5",
|
"istanbul": "0.4.5",
|
||||||
"istanbul-instrumenter-loader": "^3.0.1",
|
"istanbul-instrumenter-loader": "^3.0.1",
|
||||||
"jquery": "3.4.1",
|
"jquery": "3.4.1",
|
||||||
"jsdoc": "3.6.3",
|
"jsdoc": "3.6.3",
|
||||||
"jsdoc-plugin-typescript": "2.0.3",
|
"jsdoc-plugin-typescript": "^2.0.5",
|
||||||
"karma": "^4.1.0",
|
"karma": "^4.4.1",
|
||||||
"karma-chrome-launcher": "3.1.0",
|
"karma-chrome-launcher": "3.1.0",
|
||||||
"karma-coverage": "^2.0.1",
|
"karma-coverage": "^2.0.1",
|
||||||
"karma-coverage-istanbul-reporter": "^2.0.5",
|
"karma-coverage-istanbul-reporter": "^2.1.0",
|
||||||
"karma-firefox-launcher": "^1.1.0",
|
"karma-firefox-launcher": "^1.1.0",
|
||||||
"karma-mocha": "1.3.0",
|
"karma-mocha": "1.3.0",
|
||||||
"karma-sourcemap-loader": "^0.3.7",
|
"karma-sourcemap-loader": "^0.3.7",
|
||||||
"karma-webpack": "^4.0.0-rc.2",
|
"karma-webpack": "^4.0.0-rc.2",
|
||||||
"loglevelnext": "^3.0.1",
|
"loglevelnext": "^3.0.1",
|
||||||
"marked": "0.7.0",
|
"marked": "0.7.0",
|
||||||
"mocha": "6.2.1",
|
"mocha": "6.2.2",
|
||||||
"ol-mapbox-style": "^5.0.0-beta.3",
|
"ol-mapbox-style": "^5.0.2",
|
||||||
"pixelmatch": "^5.0.0",
|
"pixelmatch": "^5.1.0",
|
||||||
"pngjs": "^3.4.0",
|
"pngjs": "^3.4.0",
|
||||||
"proj4": "2.5.0",
|
"proj4": "2.5.0",
|
||||||
"puppeteer": "~1.20.0",
|
"puppeteer": "~1.20.0",
|
||||||
"rollup": "^1.12.0",
|
"rollup": "^1.25.1",
|
||||||
"rollup-plugin-babel": "^4.3.2",
|
"rollup-plugin-babel": "^4.3.3",
|
||||||
"rollup-plugin-commonjs": "^10.0.0",
|
"rollup-plugin-commonjs": "^10.0.0",
|
||||||
"rollup-plugin-node-resolve": "^5.0.0",
|
"rollup-plugin-node-resolve": "^5.2.0",
|
||||||
"rollup-plugin-terser": "^5.0.0",
|
"rollup-plugin-terser": "^5.0.0",
|
||||||
"serve-static": "^1.14.0",
|
"serve-static": "^1.14.0",
|
||||||
"shx": "^0.3.2",
|
"shx": "^0.3.2",
|
||||||
@@ -98,11 +98,11 @@
|
|||||||
"typescript": "3.5.3",
|
"typescript": "3.5.3",
|
||||||
"url-polyfill": "^1.1.5",
|
"url-polyfill": "^1.1.5",
|
||||||
"walk": "^2.3.9",
|
"walk": "^2.3.9",
|
||||||
"webpack": "4.41.0",
|
"webpack": "4.41.2",
|
||||||
"webpack-cli": "^3.3.2",
|
"webpack-cli": "^3.3.2",
|
||||||
"webpack-dev-middleware": "^3.6.2",
|
"webpack-dev-middleware": "^3.6.2",
|
||||||
"webpack-dev-server": "^3.3.1",
|
"webpack-dev-server": "^3.3.1",
|
||||||
"yargs": "^14.0.0"
|
"yargs": "^14.2.0"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "openlayers",
|
"extends": "openlayers",
|
||||||
|
|||||||
BIN
rendering/cases/map-text-align/expected.png
Normal file
BIN
rendering/cases/map-text-align/expected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
26
rendering/cases/map-text-align/index.html
Normal file
26
rendering/cases/map-text-align/index.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.ol-control {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<script src="main.js"></script>
|
||||||
|
</body>
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
24
rendering/cases/map-text-align/main.js
Normal file
24
rendering/cases/map-text-align/main.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import Map from '../../../src/ol/Map.js';
|
||||||
|
import View from '../../../src/ol/View.js';
|
||||||
|
import TileLayer from '../../../src/ol/layer/Tile.js';
|
||||||
|
import {fromLonLat} from '../../../src/ol/proj.js';
|
||||||
|
import XYZ from '../../../src/ol/source/XYZ.js';
|
||||||
|
|
||||||
|
|
||||||
|
new Map({
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new XYZ({
|
||||||
|
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
],
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
rotation: Math.PI / 3,
|
||||||
|
center: fromLonLat([8.6, 50.1]),
|
||||||
|
zoom: 3
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
render();
|
||||||
@@ -46,7 +46,7 @@ class ImageCanvas extends ImageBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {Error}
|
* @type {?Error}
|
||||||
*/
|
*/
|
||||||
this.error_ = null;
|
this.error_ = null;
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class ImageCanvas extends ImageBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get any error associated with asynchronous rendering.
|
* Get any error associated with asynchronous rendering.
|
||||||
* @return {Error} Any error that occurred during rendering.
|
* @return {?Error} Any error that occurred during rendering.
|
||||||
*/
|
*/
|
||||||
getError() {
|
getError() {
|
||||||
return this.error_;
|
return this.error_;
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ class MapBrowserEventHandler extends EventTarget {
|
|||||||
this.down_ = null;
|
this.down_ = null;
|
||||||
|
|
||||||
const element = this.map_.getViewport();
|
const element = this.map_.getViewport();
|
||||||
element.setAttribute('touch-action', 'none');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {number}
|
* @type {number}
|
||||||
|
|||||||
@@ -131,6 +131,17 @@ import {toUserCoordinate, fromUserCoordinate} from './proj.js';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} element Element.
|
||||||
|
* @param {string} touchAction Value for `touch-action'.
|
||||||
|
*/
|
||||||
|
function setTouchAction(element, touchAction) {
|
||||||
|
element.style.msTouchAction = touchAction;
|
||||||
|
element.style.touchAction = touchAction;
|
||||||
|
element.setAttribute('touch-action', touchAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fires import("./MapBrowserEvent.js").MapBrowserEvent
|
* @fires import("./MapBrowserEvent.js").MapBrowserEvent
|
||||||
* @fires import("./MapEvent.js").MapEvent
|
* @fires import("./MapEvent.js").MapEvent
|
||||||
@@ -246,9 +257,7 @@ class PluggableMap extends BaseObject {
|
|||||||
this.viewport_.style.overflow = 'hidden';
|
this.viewport_.style.overflow = 'hidden';
|
||||||
this.viewport_.style.width = '100%';
|
this.viewport_.style.width = '100%';
|
||||||
this.viewport_.style.height = '100%';
|
this.viewport_.style.height = '100%';
|
||||||
// prevent page zoom on IE >= 10 browsers
|
|
||||||
this.viewport_.style.msTouchAction = 'none';
|
|
||||||
this.viewport_.style.touchAction = 'none';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -296,6 +305,12 @@ class PluggableMap extends BaseObject {
|
|||||||
*/
|
*/
|
||||||
this.keyHandlerKeys_ = null;
|
this.keyHandlerKeys_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {?Array<import("./events.js").EventsKey>}
|
||||||
|
*/
|
||||||
|
this.focusHandlerKeys_ = null;
|
||||||
|
|
||||||
const handleBrowserEvent = this.handleBrowserEvent.bind(this);
|
const handleBrowserEvent = this.handleBrowserEvent.bind(this);
|
||||||
this.viewport_.addEventListener(EventType.CONTEXTMENU, handleBrowserEvent, false);
|
this.viewport_.addEventListener(EventType.CONTEXTMENU, handleBrowserEvent, false);
|
||||||
this.viewport_.addEventListener(EventType.WHEEL, handleBrowserEvent, false);
|
this.viewport_.addEventListener(EventType.WHEEL, handleBrowserEvent, false);
|
||||||
@@ -312,16 +327,6 @@ class PluggableMap extends BaseObject {
|
|||||||
*/
|
*/
|
||||||
this.interactions = optionsInternal.interactions || new Collection();
|
this.interactions = optionsInternal.interactions || new Collection();
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {import("./events/Target.js").default}
|
|
||||||
*/
|
|
||||||
this.labelCache_ = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {import("./events.js").EventsKey}
|
|
||||||
*/
|
|
||||||
this.labelCacheListenerKey_;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Collection<import("./Overlay.js").default>}
|
* @type {Collection<import("./Overlay.js").default>}
|
||||||
* @private
|
* @private
|
||||||
@@ -342,7 +347,7 @@ class PluggableMap extends BaseObject {
|
|||||||
this.renderer_ = null;
|
this.renderer_ = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {function(Event): void|undefined}
|
* @type {undefined|function(Event): void}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.handleResize_;
|
this.handleResize_;
|
||||||
@@ -549,8 +554,7 @@ class PluggableMap extends BaseObject {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const coordinate = this.getCoordinateFromPixelInternal(pixel);
|
const coordinate = this.getCoordinateFromPixelInternal(pixel);
|
||||||
opt_options = opt_options !== undefined ? opt_options :
|
opt_options = opt_options !== undefined ? opt_options : {};
|
||||||
/** @type {AtPixelOptions} */ ({});
|
|
||||||
const hitTolerance = opt_options.hitTolerance !== undefined ?
|
const hitTolerance = opt_options.hitTolerance !== undefined ?
|
||||||
opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
|
opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
|
||||||
const layerFilter = opt_options.layerFilter !== undefined ?
|
const layerFilter = opt_options.layerFilter !== undefined ?
|
||||||
@@ -602,7 +606,7 @@ class PluggableMap extends BaseObject {
|
|||||||
if (!this.frameState_) {
|
if (!this.frameState_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = opt_options || /** @type {AtPixelOptions} */ ({});
|
const options = opt_options || {};
|
||||||
const hitTolerance = options.hitTolerance !== undefined ?
|
const hitTolerance = options.hitTolerance !== undefined ?
|
||||||
options.hitTolerance * this.frameState_.pixelRatio : 0;
|
options.hitTolerance * this.frameState_.pixelRatio : 0;
|
||||||
const layerFilter = options.layerFilter || TRUE;
|
const layerFilter = options.layerFilter || TRUE;
|
||||||
@@ -622,8 +626,7 @@ class PluggableMap extends BaseObject {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const coordinate = this.getCoordinateFromPixelInternal(pixel);
|
const coordinate = this.getCoordinateFromPixelInternal(pixel);
|
||||||
opt_options = opt_options !== undefined ? opt_options :
|
opt_options = opt_options !== undefined ? opt_options : {};
|
||||||
/** @type {AtPixelOptions} */ ({});
|
|
||||||
const layerFilter = opt_options.layerFilter !== undefined ? opt_options.layerFilter : TRUE;
|
const layerFilter = opt_options.layerFilter !== undefined ? opt_options.layerFilter : TRUE;
|
||||||
const hitTolerance = opt_options.hitTolerance !== undefined ?
|
const hitTolerance = opt_options.hitTolerance !== undefined ?
|
||||||
opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
|
opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
|
||||||
@@ -1040,6 +1043,12 @@ class PluggableMap extends BaseObject {
|
|||||||
targetElement = this.getTargetElement();
|
targetElement = this.getTargetElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.focusHandlerKeys_) {
|
||||||
|
for (let i = 0, ii = this.focusHandlerKeys_.length; i < ii; ++i) {
|
||||||
|
unlistenByKey(this.focusHandlerKeys_[i]);
|
||||||
|
}
|
||||||
|
this.focusHandlerKeys_ = null;
|
||||||
|
}
|
||||||
if (this.keyHandlerKeys_) {
|
if (this.keyHandlerKeys_) {
|
||||||
for (let i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
|
for (let i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
|
||||||
unlistenByKey(this.keyHandlerKeys_[i]);
|
unlistenByKey(this.keyHandlerKeys_[i]);
|
||||||
@@ -1068,6 +1077,15 @@ class PluggableMap extends BaseObject {
|
|||||||
if (!this.renderer_) {
|
if (!this.renderer_) {
|
||||||
this.renderer_ = this.createRenderer();
|
this.renderer_ = this.createRenderer();
|
||||||
}
|
}
|
||||||
|
let hasFocus = true;
|
||||||
|
if (targetElement.hasAttribute('tabindex')) {
|
||||||
|
hasFocus = document.activeElement === targetElement;
|
||||||
|
this.focusHandlerKeys_ = [
|
||||||
|
listen(targetElement, EventType.FOCUS, setTouchAction.bind(this, this.viewport_, 'none')),
|
||||||
|
listen(targetElement, EventType.BLUR, setTouchAction.bind(this, this.viewport_, 'auto'))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
setTouchAction(this.viewport_, hasFocus ? 'none' : 'auto');
|
||||||
|
|
||||||
const keyboardEventTarget = !this.keyboardEventTarget_ ?
|
const keyboardEventTarget = !this.keyboardEventTarget_ ?
|
||||||
targetElement : this.keyboardEventTarget_;
|
targetElement : this.keyboardEventTarget_;
|
||||||
|
|||||||
@@ -87,6 +87,11 @@ class Tile extends EventTarget {
|
|||||||
|
|
||||||
const options = opt_options ? opt_options : {};
|
const options = opt_options ? opt_options : {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ImageData}
|
||||||
|
*/
|
||||||
|
this.hitDetectionImageData = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import("./tilecoord.js").TileCoord}
|
* @type {import("./tilecoord.js").TileCoord}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {getUid} from './util.js';
|
|||||||
import Tile from './Tile.js';
|
import Tile from './Tile.js';
|
||||||
import TileState from './TileState.js';
|
import TileState from './TileState.js';
|
||||||
import {createCanvasContext2D} from './dom.js';
|
import {createCanvasContext2D} from './dom.js';
|
||||||
|
import {unlistenByKey} from './events.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,6 +88,11 @@ class VectorRenderTile extends Tile {
|
|||||||
*/
|
*/
|
||||||
this.sourceTileGrid_ = sourceTileGrid;
|
this.sourceTileGrid_ = sourceTileGrid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<import("./events.js").EventsKey>}
|
||||||
|
*/
|
||||||
|
this.sourceTileListenerKeys = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* z of the source tiles of the last getSourceTiles call.
|
* z of the source tiles of the last getSourceTiles call.
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -109,6 +115,8 @@ class VectorRenderTile extends Tile {
|
|||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
disposeInternal() {
|
disposeInternal() {
|
||||||
|
this.sourceTileListenerKeys.forEach(unlistenByKey);
|
||||||
|
this.sourceTileListenerKeys.length = 0;
|
||||||
this.removeSourceTiles_(this);
|
this.removeSourceTiles_(this);
|
||||||
for (const key in this.context_) {
|
for (const key in this.context_) {
|
||||||
const canvas = this.context_[key].canvas;
|
const canvas = this.context_[key].canvas;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const HEX_COLOR_RE_ = /^#([a-f0-9]{3}|[a-f0-9]{4}(?:[a-f0-9]{2}){0,2})$/i;
|
|||||||
* @type {RegExp}
|
* @type {RegExp}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
const NAMED_COLOR_RE_ = /^([a-z]*)$/i;
|
const NAMED_COLOR_RE_ = /^([a-z]*)$|^hsla?\(.*\)$/i;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -223,3 +223,14 @@ export function toString(color) {
|
|||||||
const a = color[3] === undefined ? 1 : color[3];
|
const a = color[3] === undefined ? 1 : color[3];
|
||||||
return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
|
return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} s String.
|
||||||
|
* @return {boolean} Whether the string is actually a valid color
|
||||||
|
*/
|
||||||
|
export function isStringColor(s) {
|
||||||
|
if (NAMED_COLOR_RE_.test(s)) {
|
||||||
|
s = fromNamed(s);
|
||||||
|
}
|
||||||
|
return HEX_COLOR_RE_.test(s) || s.indexOf('rgba(') === 0 || s.indexOf('rgb(') === 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -216,8 +216,8 @@ class OverviewMap extends Control {
|
|||||||
|
|
||||||
const computeDesiredMousePosition = function(mousePosition) {
|
const computeDesiredMousePosition = function(mousePosition) {
|
||||||
return {
|
return {
|
||||||
clientX: mousePosition.clientX - (overlayBox.offsetWidth / 2),
|
clientX: mousePosition.clientX,
|
||||||
clientY: mousePosition.clientY + (overlayBox.offsetHeight / 2)
|
clientY: mousePosition.clientY
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class ScaleLine extends Control {
|
|||||||
|
|
||||||
this.addEventListener(getChangeEventType(UNITS_PROP), this.handleUnitsChanged_);
|
this.addEventListener(getChangeEventType(UNITS_PROP), this.handleUnitsChanged_);
|
||||||
|
|
||||||
this.setUnits(/** @type {Units} */ (options.units) || Units.METRIC);
|
this.setUnits(options.units || Units.METRIC);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default {
|
|||||||
*/
|
*/
|
||||||
ERROR: 'error',
|
ERROR: 'error',
|
||||||
|
|
||||||
|
BLUR: 'blur',
|
||||||
CLEAR: 'clear',
|
CLEAR: 'clear',
|
||||||
CONTEXTMENU: 'contextmenu',
|
CONTEXTMENU: 'contextmenu',
|
||||||
CLICK: 'click',
|
CLICK: 'click',
|
||||||
@@ -28,6 +29,7 @@ export default {
|
|||||||
DRAGENTER: 'dragenter',
|
DRAGENTER: 'dragenter',
|
||||||
DRAGOVER: 'dragover',
|
DRAGOVER: 'dragover',
|
||||||
DROP: 'drop',
|
DROP: 'drop',
|
||||||
|
FOCUS: 'focus',
|
||||||
KEYDOWN: 'keydown',
|
KEYDOWN: 'keydown',
|
||||||
KEYPRESS: 'keypress',
|
KEYPRESS: 'keypress',
|
||||||
LOAD: 'load',
|
LOAD: 'load',
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class Target extends Disposable {
|
|||||||
* @private
|
* @private
|
||||||
* @type {*}
|
* @type {*}
|
||||||
*/
|
*/
|
||||||
this.target_ = opt_target;
|
this.eventTarget_ = opt_target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -96,7 +96,7 @@ class Target extends Disposable {
|
|||||||
const evt = typeof event === 'string' ? new Event(event) : event;
|
const evt = typeof event === 'string' ? new Event(event) : event;
|
||||||
const type = evt.type;
|
const type = evt.type;
|
||||||
if (!evt.target) {
|
if (!evt.target) {
|
||||||
evt.target = this.target_ || this;
|
evt.target = this.eventTarget_ || this;
|
||||||
}
|
}
|
||||||
const listeners = this.listeners_[type];
|
const listeners = this.listeners_[type];
|
||||||
let propagate;
|
let propagate;
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ class Bbox extends Filter {
|
|||||||
* @type {import("../../extent.js").Extent}
|
* @type {import("../../extent.js").Extent}
|
||||||
*/
|
*/
|
||||||
this.extent = extent;
|
this.extent = extent;
|
||||||
|
if (extent.length !== 4) {
|
||||||
|
throw new Error('Expected an extent with four values ([minX, minY, maxX, maxY])');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string|undefined}
|
* @type {string|undefined}
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ export function VOID() {}
|
|||||||
export function memoizeOne(fn) {
|
export function memoizeOne(fn) {
|
||||||
let called = false;
|
let called = false;
|
||||||
|
|
||||||
/** @type ReturnType */
|
/** @type {ReturnType} */
|
||||||
let lastResult;
|
let lastResult;
|
||||||
|
|
||||||
/** @type Array<any> */
|
/** @type {Array<any>} */
|
||||||
let lastArgs;
|
let lastArgs;
|
||||||
|
|
||||||
let lastThis;
|
let lastThis;
|
||||||
|
|||||||
@@ -229,12 +229,13 @@ class Geometry extends BaseObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a transform function to each coordinate of the geometry.
|
* Apply a transform function to the coordinates of the geometry.
|
||||||
* The geometry is modified in place.
|
* The geometry is modified in place.
|
||||||
* If you do not want the geometry modified in place, first `clone()` it and
|
* If you do not want the geometry modified in place, first `clone()` it and
|
||||||
* then use this function on the clone.
|
* then use this function on the clone.
|
||||||
* @abstract
|
* @abstract
|
||||||
* @param {import("../proj.js").TransformFunction} transformFn Transform.
|
* @param {import("../proj.js").TransformFunction} transformFn Transform function.
|
||||||
|
* Called with a flat array of geometry coordinates.
|
||||||
*/
|
*/
|
||||||
applyTransform(transformFn) {
|
applyTransform(transformFn) {
|
||||||
abstract();
|
abstract();
|
||||||
|
|||||||
@@ -187,7 +187,12 @@ class SimpleGeometry extends Geometry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* Apply a transform function to the coordinates of the geometry.
|
||||||
|
* The geometry is modified in place.
|
||||||
|
* If you do not want the geometry modified in place, first `clone()` it and
|
||||||
|
* then use this function on the clone.
|
||||||
|
* @param {import("../proj.js").TransformFunction} transformFn Transform function.
|
||||||
|
* Called with a flat array of geometry coordinates.
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
applyTransform(transformFn) {
|
applyTransform(transformFn) {
|
||||||
@@ -198,7 +203,10 @@ class SimpleGeometry extends Geometry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* Rotate the geometry around a given coordinate. This modifies the geometry
|
||||||
|
* coordinates in place.
|
||||||
|
* @param {number} angle Rotation angle in radians.
|
||||||
|
* @param {import("../coordinate.js").Coordinate} anchor The rotation center.
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
rotate(angle, anchor) {
|
rotate(angle, anchor) {
|
||||||
@@ -213,7 +221,13 @@ class SimpleGeometry extends Geometry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* Scale the geometry (with an optional origin). This modifies the geometry
|
||||||
|
* coordinates in place.
|
||||||
|
* @param {number} sx The scaling factor in the x-direction.
|
||||||
|
* @param {number=} opt_sy The scaling factor in the y-direction (defaults to
|
||||||
|
* sx).
|
||||||
|
* @param {import("../coordinate.js").Coordinate=} opt_anchor The scale origin (defaults to the center
|
||||||
|
* of the geometry extent).
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
scale(sx, opt_sy, opt_anchor) {
|
scale(sx, opt_sy, opt_anchor) {
|
||||||
@@ -236,7 +250,10 @@ class SimpleGeometry extends Geometry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* Translate the geometry. This modifies the geometry coordinates in place. If
|
||||||
|
* instead you want a new geometry, first `clone()` this geometry.
|
||||||
|
* @param {number} deltaX Delta X.
|
||||||
|
* @param {number} deltaY Delta Y.
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
translate(deltaX, deltaY) {
|
translate(deltaX, deltaY) {
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ import {getValues} from '../obj.js';
|
|||||||
* propagation of the event to other interactions in the map's interactions
|
* propagation of the event to other interactions in the map's interactions
|
||||||
* chain.
|
* chain.
|
||||||
* @property {function(import("../MapBrowserPointerEvent.js").default)} [handleMoveEvent]
|
* @property {function(import("../MapBrowserPointerEvent.js").default)} [handleMoveEvent]
|
||||||
* Function handling "move" events. This function is called on "move" events,
|
* Function handling "move" events. This function is called on "move" events.
|
||||||
* also during a drag sequence (so during a drag sequence both the
|
* This functions is also called during a drag sequence, so during a drag
|
||||||
* `handleDragEvent` function and this function are called).
|
* sequence both the `handleDragEvent` function and this function are called.
|
||||||
|
* If `handleDownEvent` is defined and it returns true this function will not
|
||||||
|
* be called during a drag sequence.
|
||||||
* @property {function(import("../MapBrowserPointerEvent.js").default):boolean} [handleUpEvent]
|
* @property {function(import("../MapBrowserPointerEvent.js").default):boolean} [handleUpEvent]
|
||||||
* Function handling "up" events. If the function returns `false` then the
|
* Function handling "up" events. If the function returns `false` then the
|
||||||
* current drag sequence is stopped.
|
* current drag sequence is stopped.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {getUid} from '../util.js';
|
|||||||
import CollectionEventType from '../CollectionEventType.js';
|
import CollectionEventType from '../CollectionEventType.js';
|
||||||
import {extend, includes} from '../array.js';
|
import {extend, includes} from '../array.js';
|
||||||
import Event from '../events/Event.js';
|
import Event from '../events/Event.js';
|
||||||
import {singleClick, never, shiftKeyOnly, pointerMove} from '../events/condition.js';
|
import {singleClick, never, shiftKeyOnly} from '../events/condition.js';
|
||||||
import {TRUE} from '../functions.js';
|
import {TRUE} from '../functions.js';
|
||||||
import GeometryType from '../geom/GeometryType.js';
|
import GeometryType from '../geom/GeometryType.js';
|
||||||
import Interaction from './Interaction.js';
|
import Interaction from './Interaction.js';
|
||||||
@@ -61,6 +61,7 @@ const SelectEventType = {
|
|||||||
* @property {import("../style/Style.js").StyleLike} [style]
|
* @property {import("../style/Style.js").StyleLike} [style]
|
||||||
* Style for the selected features. By default the default edit style is used
|
* Style for the selected features. By default the default edit style is used
|
||||||
* (see {@link module:ol/style}).
|
* (see {@link module:ol/style}).
|
||||||
|
* If set to `false` the selected feature's style will not change.
|
||||||
* @property {import("../events/condition.js").Condition} [removeCondition] A function
|
* @property {import("../events/condition.js").Condition} [removeCondition] A function
|
||||||
* that takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a
|
* that takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a
|
||||||
* boolean to indicate whether that event should be handled.
|
* boolean to indicate whether that event should be handled.
|
||||||
@@ -206,7 +207,7 @@ class Select extends Interaction {
|
|||||||
* @private
|
* @private
|
||||||
* @type {import("../style/Style.js").default|Array.<import("../style/Style.js").default>|import("../style/Style.js").StyleFunction|null}
|
* @type {import("../style/Style.js").default|Array.<import("../style/Style.js").default>|import("../style/Style.js").StyleFunction|null}
|
||||||
*/
|
*/
|
||||||
this.style_ = options.style ? options.style : getDefaultStyleFunction();
|
this.style_ = options.style !== undefined ? options.style : getDefaultStyleFunction();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An association between selected feature (key)
|
* An association between selected feature (key)
|
||||||
@@ -467,7 +468,7 @@ function handleEvent(mapBrowserEvent) {
|
|||||||
new SelectEvent(SelectEventType.SELECT,
|
new SelectEvent(SelectEventType.SELECT,
|
||||||
selected, deselected, mapBrowserEvent));
|
selected, deselected, mapBrowserEvent));
|
||||||
}
|
}
|
||||||
return pointerMove(mapBrowserEvent);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,24 @@ class BaseVectorLayer extends Layer {
|
|||||||
return this.declutter_;
|
return this.declutter_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the topmost feature that intersects the given pixel on the viewport. Returns a promise
|
||||||
|
* that resolves with an array of features. The array will either contain the topmost feature
|
||||||
|
* when a hit was detected, or it will be empty.
|
||||||
|
*
|
||||||
|
* The hit detection algorithm used for this method is optimized for performance, but is less
|
||||||
|
* accurate than the one used in {@link import("../PluggableMap.js").default#getFeaturesAtPixel}: Text
|
||||||
|
* is not considered, and icons are only represented by their bounding box instead of the exact
|
||||||
|
* image.
|
||||||
|
*
|
||||||
|
* @param {import("../pixel.js").Pixel} pixel Pixel.
|
||||||
|
* @return {Promise<Array<import("../Feature").default>>} Promise that resolves with an array of features.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
getFeatures(pixel) {
|
||||||
|
return super.getFeatures(pixel);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {number|undefined} Render buffer.
|
* @return {number|undefined} Render buffer.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -318,23 +318,12 @@ class Graticule extends VectorLayer {
|
|||||||
options.latLabelPosition;
|
options.latLabelPosition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Object.<string,Style>}
|
* @type {Style}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.lonLabelStyleCache_ = {};
|
this.lonLabelStyleBase_ = new Style({
|
||||||
|
text: options.lonLabelStyle !== undefined ? options.lonLabelStyle.clone() :
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {import("../Feature").default} feature Feature
|
|
||||||
* @return {Style} style
|
|
||||||
*/
|
|
||||||
this.lonLabelStyle_ = function(feature) {
|
|
||||||
const label = feature.get('graticule_label');
|
|
||||||
if (!this.lonLabelStyleCache_[label]) {
|
|
||||||
this.lonLabelStyleCache_[label] = new Style({
|
|
||||||
text: options.lonLabelStyle !== undefined ? options.lonLabelStyle :
|
|
||||||
new Text({
|
new Text({
|
||||||
text: label,
|
|
||||||
font: '12px Calibri,sans-serif',
|
font: '12px Calibri,sans-serif',
|
||||||
textBaseline: 'bottom',
|
textBaseline: 'bottom',
|
||||||
fill: new Fill({
|
fill: new Fill({
|
||||||
@@ -346,28 +335,25 @@ class Graticule extends VectorLayer {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return this.lonLabelStyleCache_[label];
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {Object.<string,Style>}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this.latLabelStyleCache_ = {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {import("../Feature").default} feature Feature
|
* @param {import("../Feature").default} feature Feature
|
||||||
* @return {Style} style
|
* @return {Style} style
|
||||||
*/
|
*/
|
||||||
this.latLabelStyle_ = function(feature) {
|
this.lonLabelStyle_ = function(feature) {
|
||||||
const label = feature.get('graticule_label');
|
const label = feature.get('graticule_label');
|
||||||
if (!this.latLabelStyleCache_[label]) {
|
this.lonLabelStyleBase_.getText().setText(label);
|
||||||
this.latLabelStyleCache_[label] = new Style({
|
return this.lonLabelStyleBase_;
|
||||||
text: options.latLabelStyle !== undefined ? options.latLabelStyle :
|
}.bind(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Style}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.latLabelStyleBase_ = new Style({
|
||||||
|
text: options.latLabelStyle !== undefined ? options.latLabelStyle.clone() :
|
||||||
new Text({
|
new Text({
|
||||||
text: label,
|
|
||||||
font: '12px Calibri,sans-serif',
|
font: '12px Calibri,sans-serif',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
fill: new Fill({
|
fill: new Fill({
|
||||||
@@ -379,8 +365,16 @@ class Graticule extends VectorLayer {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return this.latLabelStyleCache_[label];
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {import("../Feature").default} feature Feature
|
||||||
|
* @return {Style} style
|
||||||
|
*/
|
||||||
|
this.latLabelStyle_ = function(feature) {
|
||||||
|
const label = feature.get('graticule_label');
|
||||||
|
this.latLabelStyleBase_.getText().setText(label);
|
||||||
|
return this.latLabelStyleBase_;
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
this.meridiansLabels_ = [];
|
this.meridiansLabels_ = [];
|
||||||
@@ -421,7 +415,7 @@ class Graticule extends VectorLayer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import("../extent.js").Extent}
|
* @type {?import("../extent.js").Extent}
|
||||||
*/
|
*/
|
||||||
this.renderedExtent_ = null;
|
this.renderedExtent_ = null;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import {getChangeEventType} from '../Object.js';
|
import {getChangeEventType} from '../Object.js';
|
||||||
import {createCanvasContext2D} from '../dom.js';
|
import {createCanvasContext2D} from '../dom.js';
|
||||||
import VectorLayer from './Vector.js';
|
import VectorLayer from './Vector.js';
|
||||||
|
import {clamp} from '../math.js';
|
||||||
import {assign} from '../obj.js';
|
import {assign} from '../obj.js';
|
||||||
import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
|
import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
|
||||||
|
|
||||||
@@ -180,7 +181,10 @@ class Heatmap extends VectorLayer {
|
|||||||
attributes: [
|
attributes: [
|
||||||
{
|
{
|
||||||
name: 'weight',
|
name: 'weight',
|
||||||
callback: this.weightFunction_
|
callback: function(feature) {
|
||||||
|
const weight = this.weightFunction_(feature);
|
||||||
|
return weight !== undefined ? clamp(weight, 0, 1) : 1;
|
||||||
|
}.bind(this)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
vertexShader: `
|
vertexShader: `
|
||||||
@@ -220,6 +224,51 @@ class Heatmap extends VectorLayer {
|
|||||||
float alpha = smoothstep(0.0, 1.0, value) * v_weight;
|
float alpha = smoothstep(0.0, 1.0, value) * v_weight;
|
||||||
gl_FragColor = vec4(alpha, alpha, alpha, alpha);
|
gl_FragColor = vec4(alpha, alpha, alpha, alpha);
|
||||||
}`,
|
}`,
|
||||||
|
hitVertexShader: `
|
||||||
|
precision mediump float;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
|
uniform float u_size;
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute float a_index;
|
||||||
|
attribute float a_weight;
|
||||||
|
attribute vec4 a_hitColor;
|
||||||
|
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
varying float v_weight;
|
||||||
|
varying vec4 v_hitColor;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||||
|
float offsetX = a_index == 0.0 || a_index == 3.0 ? -u_size / 2.0 : u_size / 2.0;
|
||||||
|
float offsetY = a_index == 0.0 || a_index == 1.0 ? -u_size / 2.0 : u_size / 2.0;
|
||||||
|
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
|
||||||
|
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
||||||
|
float u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
|
float v = a_index == 0.0 || a_index == 1.0 ? 0.0 : 1.0;
|
||||||
|
v_texCoord = vec2(u, v);
|
||||||
|
v_hitColor = a_hitColor;
|
||||||
|
v_weight = a_weight;
|
||||||
|
}`,
|
||||||
|
hitFragmentShader: `
|
||||||
|
precision mediump float;
|
||||||
|
uniform float u_blurSlope;
|
||||||
|
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
varying float v_weight;
|
||||||
|
varying vec4 v_hitColor;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
vec2 texCoord = v_texCoord * 2.0 - vec2(1.0, 1.0);
|
||||||
|
float sqRadius = texCoord.x * texCoord.x + texCoord.y * texCoord.y;
|
||||||
|
float value = (1.0 - sqrt(sqRadius)) * u_blurSlope;
|
||||||
|
float alpha = smoothstep(0.0, 1.0, value) * v_weight;
|
||||||
|
if (alpha < 0.05) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_FragColor = v_hitColor;
|
||||||
|
}`,
|
||||||
uniforms: {
|
uniforms: {
|
||||||
u_size: function() {
|
u_size: function() {
|
||||||
return (this.get(Property.RADIUS) + this.get(Property.BLUR)) * 2;
|
return (this.get(Property.RADIUS) + this.get(Property.BLUR)) * 2;
|
||||||
|
|||||||
@@ -192,6 +192,15 @@ class Layer extends BaseLayer {
|
|||||||
this.changed();
|
this.changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../pixel").Pixel} pixel Pixel.
|
||||||
|
* @return {Promise<Array<import("../Feature").default>>} Promise that resolves with
|
||||||
|
* an array of features.
|
||||||
|
*/
|
||||||
|
getFeatures(pixel) {
|
||||||
|
return this.renderer_.getFeatures(pixel);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In charge to manage the rendering of the layer. One layer type is
|
* In charge to manage the rendering of the layer. One layer type is
|
||||||
* bounded with one layer renderer.
|
* bounded with one layer renderer.
|
||||||
|
|||||||
@@ -116,6 +116,24 @@ class VectorTileLayer extends BaseVectorLayer {
|
|||||||
return new CanvasVectorTileLayerRenderer(this);
|
return new CanvasVectorTileLayerRenderer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the topmost feature that intersects the given pixel on the viewport. Returns a promise
|
||||||
|
* that resolves with an array of features. The array will either contain the topmost feature
|
||||||
|
* when a hit was detected, or it will be empty.
|
||||||
|
*
|
||||||
|
* The hit detection algorithm used for this method is optimized for performance, but is less
|
||||||
|
* accurate than the one used in {@link import("../PluggableMap.js").default#getFeaturesAtPixel}: Text
|
||||||
|
* is not considered, and icons are only represented by their bounding box instead of the exact
|
||||||
|
* image.
|
||||||
|
*
|
||||||
|
* @param {import("../pixel.js").Pixel} pixel Pixel.
|
||||||
|
* @return {Promise<Array<import("../Feature").default>>} Promise that resolves with an array of features.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
getFeatures(pixel) {
|
||||||
|
return super.getFeatures(pixel);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {VectorTileRenderType} The render mode.
|
* @return {VectorTileRenderType} The render mode.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import {assign} from '../obj.js';
|
import {assign} from '../obj.js';
|
||||||
import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
|
import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
|
||||||
import {getSymbolFragmentShader, getSymbolVertexShader, parseSymbolStyle} from '../webgl/ShaderBuilder.js';
|
import {parseLiteralStyle} from '../webgl/ShaderBuilder.js';
|
||||||
import {assert} from '../asserts.js';
|
|
||||||
import Layer from './Layer.js';
|
import Layer from './Layer.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -25,6 +24,8 @@ import Layer from './Layer.js';
|
|||||||
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
|
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
|
||||||
* be visible.
|
* be visible.
|
||||||
* @property {import("../source/Vector.js").default} [source] Source.
|
* @property {import("../source/Vector.js").default} [source] Source.
|
||||||
|
* @property {boolean} [disableHitDetection] Setting this to true will provide a slight performance boost, but will
|
||||||
|
* prevent all hit detection on the layer.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@@ -72,24 +73,31 @@ class WebGLPointsLayer extends Layer {
|
|||||||
super(baseOptions);
|
super(baseOptions);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('../style/LiteralStyle.js').LiteralStyle}
|
* @private
|
||||||
|
* @type {import('../webgl/ShaderBuilder.js').StyleParseResult}
|
||||||
*/
|
*/
|
||||||
this.style = options.style;
|
this.parseResult_ = parseLiteralStyle(options.style);
|
||||||
|
|
||||||
assert(this.style.symbol !== undefined, 65);
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.hitDetectionDisabled_ = !!options.disableHitDetection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
createRenderer() {
|
createRenderer() {
|
||||||
const parseResult = parseSymbolStyle(this.style.symbol);
|
|
||||||
|
|
||||||
return new WebGLPointsLayerRenderer(this, {
|
return new WebGLPointsLayerRenderer(this, {
|
||||||
vertexShader: getSymbolVertexShader(parseResult.params),
|
vertexShader: this.parseResult_.builder.getSymbolVertexShader(),
|
||||||
fragmentShader: getSymbolFragmentShader(parseResult.params),
|
fragmentShader: this.parseResult_.builder.getSymbolFragmentShader(),
|
||||||
uniforms: parseResult.uniforms,
|
hitVertexShader: !this.hitDetectionDisabled_ &&
|
||||||
attributes: parseResult.attributes
|
this.parseResult_.builder.getSymbolVertexShader(true),
|
||||||
|
hitFragmentShader: !this.hitDetectionDisabled_ &&
|
||||||
|
this.parseResult_.builder.getSymbolFragmentShader(true),
|
||||||
|
uniforms: this.parseResult_.uniforms,
|
||||||
|
attributes: this.parseResult_.attributes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,15 +182,12 @@
|
|||||||
.ol-attribution ul {
|
.ol-attribution ul {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 .5em;
|
padding: 0 .5em;
|
||||||
font-size: .7rem;
|
|
||||||
line-height: 1.375em;
|
|
||||||
color: #000;
|
color: #000;
|
||||||
text-shadow: 0 0 2px #fff;
|
text-shadow: 0 0 2px #fff;
|
||||||
}
|
}
|
||||||
.ol-attribution li {
|
.ol-attribution li {
|
||||||
display: inline;
|
display: inline;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
line-height: inherit;
|
|
||||||
}
|
}
|
||||||
.ol-attribution li:not(:last-child):after {
|
.ol-attribution li:not(:last-child):after {
|
||||||
content: " ";
|
content: " ";
|
||||||
@@ -213,8 +210,6 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
border-radius: 4px 0 0;
|
border-radius: 4px 0 0;
|
||||||
height: 1.1em;
|
|
||||||
line-height: 1em;
|
|
||||||
}
|
}
|
||||||
.ol-attribution.ol-uncollapsible img {
|
.ol-attribution.ol-uncollapsible img {
|
||||||
margin-top: -.2em;
|
margin-top: -.2em;
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ export function transformWithProjections(point, sourceProjection, destinationPro
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Projection}
|
* @type {?Projection}
|
||||||
*/
|
*/
|
||||||
let userProjection = null;
|
let userProjection = null;
|
||||||
|
|
||||||
@@ -526,7 +526,7 @@ export function clearUserProjection() {
|
|||||||
* Get the projection for coordinates supplied from and returned by API methods.
|
* Get the projection for coordinates supplied from and returned by API methods.
|
||||||
* Note that this method is not yet a part of the stable API. Support for user
|
* Note that this method is not yet a part of the stable API. Support for user
|
||||||
* projections is not yet complete and should be considered experimental.
|
* projections is not yet complete and should be considered experimental.
|
||||||
* @returns {Projection} The user projection (or null if not set).
|
* @returns {?Projection} The user projection (or null if not set).
|
||||||
*/
|
*/
|
||||||
export function getUserProjection() {
|
export function getUserProjection() {
|
||||||
return userProjection;
|
return userProjection;
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ class RenderEvent extends Event {
|
|||||||
this.frameState = opt_frameState;
|
this.frameState = opt_frameState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Canvas context. Only available when a Canvas renderer is used, null
|
* Canvas context. Not available when the event is dispatched by the map. Only available
|
||||||
* otherwise.
|
* when a Canvas renderer is used, null otherwise.
|
||||||
* @type {CanvasRenderingContext2D|null|undefined}
|
* @type {CanvasRenderingContext2D|null|undefined}
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggered before layers are rendered.
|
* Triggered before layers are rendered.
|
||||||
|
* The event object will not have a `context` set.
|
||||||
* @event module:ol/render/Event~RenderEvent#precompose
|
* @event module:ol/render/Event~RenderEvent#precompose
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
@@ -30,6 +31,7 @@ export default {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggered after all layers are rendered.
|
* Triggered after all layers are rendered.
|
||||||
|
* The event object will not have a `context` set.
|
||||||
* @event module:ol/render/Event~RenderEvent#postcompose
|
* @event module:ol/render/Event~RenderEvent#postcompose
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
@@ -38,6 +40,7 @@ export default {
|
|||||||
/**
|
/**
|
||||||
* Triggered when rendering is complete, i.e. all sources and tiles have
|
* Triggered when rendering is complete, i.e. all sources and tiles have
|
||||||
* finished loading for the current viewport, and all tiles are faded in.
|
* finished loading for the current viewport, and all tiles are faded in.
|
||||||
|
* The event object will not have a `context` set.
|
||||||
* @event module:ol/render/Event~RenderEvent#rendercomplete
|
* @event module:ol/render/Event~RenderEvent#rendercomplete
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import VectorContext from '../VectorContext.js';
|
|||||||
import {defaultTextAlign, defaultFillStyle, defaultLineCap, defaultLineDash, defaultLineDashOffset, defaultLineJoin, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline, defaultFont} from '../canvas.js';
|
import {defaultTextAlign, defaultFillStyle, defaultLineCap, defaultLineDash, defaultLineDashOffset, defaultLineJoin, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline, defaultFont} from '../canvas.js';
|
||||||
import {create as createTransform, compose as composeTransform} from '../../transform.js';
|
import {create as createTransform, compose as composeTransform} from '../../transform.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @classdesc
|
* @classdesc
|
||||||
* A concrete subclass of {@link module:ol/render/VectorContext} that implements
|
* A concrete subclass of {@link module:ol/render/VectorContext} that implements
|
||||||
@@ -438,6 +439,13 @@ class CanvasImmediateRenderer extends VectorContext {
|
|||||||
this.setTextStyle(style.getText());
|
this.setTextStyle(style.getText());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../transform.js").Transform} transform Transform.
|
||||||
|
*/
|
||||||
|
setTransform(transform) {
|
||||||
|
this.transform_ = transform;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a geometry into the canvas. Call
|
* Render a geometry into the canvas. Call
|
||||||
* {@link module:ol/render/canvas/Immediate#setStyle} first to set the rendering style.
|
* {@link module:ol/render/canvas/Immediate#setStyle} first to set the rendering style.
|
||||||
|
|||||||
146
src/ol/render/canvas/hitdetect.js
Normal file
146
src/ol/render/canvas/hitdetect.js
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/canvas/hitdetet
|
||||||
|
*/
|
||||||
|
|
||||||
|
import CanvasImmediateRenderer from './Immediate.js';
|
||||||
|
import {createCanvasContext2D} from '../../dom.js';
|
||||||
|
import {Icon} from '../../style.js';
|
||||||
|
import IconAnchorUnits from '../../style/IconAnchorUnits.js';
|
||||||
|
import GeometryType from '../../geom/GeometryType.js';
|
||||||
|
import {intersects} from '../../extent.js';
|
||||||
|
import {numberSafeCompareFunction} from '../../array.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../size.js").Size} size Canvas size in css pixels.
|
||||||
|
* @param {Array<import("../../transform.js").Transform>} transforms Transforms
|
||||||
|
* for rendering features to all worlds of the viewport, from coordinates to css
|
||||||
|
* pixels.
|
||||||
|
* @param {Array<import("../../Feature.js").FeatureLike>} features
|
||||||
|
* Features to consider for hit detection.
|
||||||
|
* @param {import("../../style/Style.js").StyleFunction|undefined} styleFunction
|
||||||
|
* Layer style function.
|
||||||
|
* @param {import("../../extent.js").Extent} extent Extent.
|
||||||
|
* @param {number} resolution Resolution.
|
||||||
|
* @param {number} rotation Rotation.
|
||||||
|
* @return {ImageData} Hit detection image data.
|
||||||
|
*/
|
||||||
|
export function createHitDetectionImageData(size, transforms, features, styleFunction, extent, resolution, rotation) {
|
||||||
|
const width = size[0] / 2;
|
||||||
|
const height = size[1] / 2;
|
||||||
|
const context = createCanvasContext2D(width, height);
|
||||||
|
context.imageSmoothingEnabled = false;
|
||||||
|
const canvas = context.canvas;
|
||||||
|
const renderer = new CanvasImmediateRenderer(context, 0.5, extent, null, rotation);
|
||||||
|
const featureCount = features.length;
|
||||||
|
// Stretch hit detection index to use the whole available color range
|
||||||
|
const indexFactor = Math.ceil((256 * 256 * 256) / featureCount);
|
||||||
|
const featuresByZIndex = {};
|
||||||
|
for (let i = 0; i < featureCount; ++i) {
|
||||||
|
const feature = features[i];
|
||||||
|
const featureStyleFunction = feature.getStyleFunction() || styleFunction;
|
||||||
|
if (!styleFunction) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let styles = featureStyleFunction(feature, resolution);
|
||||||
|
if (!Array.isArray(styles)) {
|
||||||
|
styles = [styles];
|
||||||
|
}
|
||||||
|
const index = i * indexFactor;
|
||||||
|
const color = '#' + ('000000' + index.toString(16)).slice(-6);
|
||||||
|
for (let j = 0, jj = styles.length; j < jj; ++j) {
|
||||||
|
const originalStyle = styles[j];
|
||||||
|
const style = originalStyle.clone();
|
||||||
|
const fill = style.getFill();
|
||||||
|
if (fill) {
|
||||||
|
fill.setColor(color);
|
||||||
|
}
|
||||||
|
const stroke = style.getStroke();
|
||||||
|
if (stroke) {
|
||||||
|
stroke.setColor(color);
|
||||||
|
}
|
||||||
|
style.setText(undefined);
|
||||||
|
const image = originalStyle.getImage();
|
||||||
|
if (image) {
|
||||||
|
const imgSize = image.getImageSize();
|
||||||
|
const imgContext = createCanvasContext2D(imgSize[0], imgSize[1]);
|
||||||
|
imgContext.fillStyle = color;
|
||||||
|
const img = imgContext.canvas;
|
||||||
|
imgContext.fillRect(0, 0, img.width, img.height);
|
||||||
|
const width = imgSize ? imgSize[0] : img.width;
|
||||||
|
const height = imgSize ? imgSize[1] : img.height;
|
||||||
|
const iconContext = createCanvasContext2D(width, height);
|
||||||
|
iconContext.drawImage(img, 0, 0);
|
||||||
|
style.setImage(new Icon({
|
||||||
|
img: img,
|
||||||
|
imgSize: imgSize,
|
||||||
|
anchor: image.getAnchor(),
|
||||||
|
anchorXUnits: IconAnchorUnits.PIXELS,
|
||||||
|
anchorYUnits: IconAnchorUnits.PIXELS,
|
||||||
|
offset: image.getOrigin(),
|
||||||
|
size: image.getSize(),
|
||||||
|
opacity: image.getOpacity(),
|
||||||
|
scale: image.getScale(),
|
||||||
|
rotation: image.getRotation(),
|
||||||
|
rotateWithView: image.getRotateWithView()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const zIndex = Number(style.getZIndex());
|
||||||
|
let byGeometryType = featuresByZIndex[zIndex];
|
||||||
|
if (!byGeometryType) {
|
||||||
|
byGeometryType = featuresByZIndex[zIndex] = {};
|
||||||
|
byGeometryType[GeometryType.POLYGON] = [];
|
||||||
|
byGeometryType[GeometryType.CIRCLE] = [];
|
||||||
|
byGeometryType[GeometryType.LINE_STRING] = [];
|
||||||
|
byGeometryType[GeometryType.POINT] = [];
|
||||||
|
}
|
||||||
|
const geometry = style.getGeometryFunction()(feature);
|
||||||
|
if (geometry && intersects(extent, geometry.getExtent())) {
|
||||||
|
byGeometryType[geometry.getType().replace('Multi', '')].push(geometry, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const zIndexKeys = Object.keys(featuresByZIndex).map(Number).sort(numberSafeCompareFunction);
|
||||||
|
for (let i = 0, ii = zIndexKeys.length; i < ii; ++i) {
|
||||||
|
const byGeometryType = featuresByZIndex[zIndexKeys[i]];
|
||||||
|
for (const type in byGeometryType) {
|
||||||
|
const geomAndStyle = byGeometryType[type];
|
||||||
|
for (let j = 0, jj = geomAndStyle.length; j < jj; j += 2) {
|
||||||
|
renderer.setStyle(geomAndStyle[j + 1]);
|
||||||
|
for (let k = 0, kk = transforms.length; k < kk; ++k) {
|
||||||
|
renderer.setTransform(transforms[k]);
|
||||||
|
renderer.drawGeometry(geomAndStyle[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return context.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../pixel").Pixel} pixel Pixel coordinate on the hit
|
||||||
|
* detection canvas in css pixels.
|
||||||
|
* @param {Array<import("../../Feature").FeatureLike>} features Features. Has to
|
||||||
|
* match the `features` array that was passed to `createHitDetectionImageData()`.
|
||||||
|
* @param {ImageData} imageData Hit detection image data generated by
|
||||||
|
* `createHitDetectionImageData()`.
|
||||||
|
* @return {Array<import("../../Feature").FeatureLike>} features Features.
|
||||||
|
*/
|
||||||
|
export function hitDetect(pixel, features, imageData) {
|
||||||
|
const resultFeatures = [];
|
||||||
|
if (imageData) {
|
||||||
|
const index = (Math.round(pixel[0] / 2) + Math.round(pixel[1] / 2) * imageData.width) * 4;
|
||||||
|
const r = imageData.data[index];
|
||||||
|
const g = imageData.data[index + 1];
|
||||||
|
const b = imageData.data[index + 2];
|
||||||
|
const a = imageData.data[index + 3];
|
||||||
|
if (a === 255) {
|
||||||
|
const i = b + (256 * (g + (256 * r)));
|
||||||
|
const indexFactor = Math.ceil((256 * 256 * 256) / features.length);
|
||||||
|
if (i % indexFactor === 0) {
|
||||||
|
resultFeatures.push(features[i / indexFactor]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultFeatures;
|
||||||
|
}
|
||||||
@@ -30,6 +30,16 @@ class LayerRenderer extends Observable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous layer level hit detection.
|
||||||
|
* @param {import("../pixel.js").Pixel} pixel Pixel.
|
||||||
|
* @return {Promise<Array<import("../Feature").default>>} Promise that resolves with
|
||||||
|
* an array of features.
|
||||||
|
*/
|
||||||
|
getFeatures(pixel) {
|
||||||
|
return abstract();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether render should be called.
|
* Determine whether render should be called.
|
||||||
* @abstract
|
* @abstract
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ class CanvasLayerRenderer extends LayerRenderer {
|
|||||||
container.appendChild(canvas);
|
container.appendChild(canvas);
|
||||||
style = canvas.style;
|
style = canvas.style;
|
||||||
style.position = 'absolute';
|
style.position = 'absolute';
|
||||||
|
style.left = '0';
|
||||||
style.transformOrigin = 'top left';
|
style.transformOrigin = 'top left';
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@@ -213,23 +214,24 @@ class CanvasLayerRenderer extends LayerRenderer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a transform for rendering to an element that will be rotated after rendering.
|
* Creates a transform for rendering to an element that will be rotated after rendering.
|
||||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {import("../../coordinate.js").Coordinate} center Center.
|
||||||
|
* @param {number} resolution Resolution.
|
||||||
|
* @param {number} rotation Rotation.
|
||||||
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {number} width Width of the rendered element (in pixels).
|
* @param {number} width Width of the rendered element (in pixels).
|
||||||
* @param {number} height Height of the rendered element (in pixels).
|
* @param {number} height Height of the rendered element (in pixels).
|
||||||
* @param {number} offsetX Offset on the x-axis in view coordinates.
|
* @param {number} offsetX Offset on the x-axis in view coordinates.
|
||||||
* @protected
|
* @protected
|
||||||
* @return {!import("../../transform.js").Transform} Transform.
|
* @return {!import("../../transform.js").Transform} Transform.
|
||||||
*/
|
*/
|
||||||
getRenderTransform(frameState, width, height, offsetX) {
|
getRenderTransform(center, resolution, rotation, pixelRatio, width, height, offsetX) {
|
||||||
const viewState = frameState.viewState;
|
|
||||||
const pixelRatio = frameState.pixelRatio;
|
|
||||||
const dx1 = width / 2;
|
const dx1 = width / 2;
|
||||||
const dy1 = height / 2;
|
const dy1 = height / 2;
|
||||||
const sx = pixelRatio / viewState.resolution;
|
const sx = pixelRatio / resolution;
|
||||||
const sy = -sx;
|
const sy = -sx;
|
||||||
const dx2 = -viewState.center[0] + offsetX;
|
const dx2 = -center[0] + offsetX;
|
||||||
const dy2 = -viewState.center[1];
|
const dy2 = -center[1];
|
||||||
return composeTransform(this.tempTransform_, dx1, dy1, sx, sy, -viewState.rotation, dx2, dy2);
|
return composeTransform(this.tempTransform_, dx1, dy1, sx, sy, -rotation, dx2, dy2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -31,10 +31,22 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {import("../../extent.js").Extent}
|
* @type {?import("../../extent.js").Extent}
|
||||||
*/
|
*/
|
||||||
this.renderedExtent_ = null;
|
this.renderedExtent_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.renderedPixelRatio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {import("../../proj/Projection.js").default}
|
||||||
|
*/
|
||||||
|
this.renderedProjection = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -342,6 +354,8 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
this.renderedResolution = tileResolution;
|
this.renderedResolution = tileResolution;
|
||||||
this.extentChanged = !this.renderedExtent_ || !equals(this.renderedExtent_, canvasExtent);
|
this.extentChanged = !this.renderedExtent_ || !equals(this.renderedExtent_, canvasExtent);
|
||||||
this.renderedExtent_ = canvasExtent;
|
this.renderedExtent_ = canvasExtent;
|
||||||
|
this.renderedPixelRatio = pixelRatio;
|
||||||
|
this.renderedProjection = projection;
|
||||||
|
|
||||||
this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio,
|
this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio,
|
||||||
projection, extent, z, tileLayer.getPreload());
|
projection, extent, z, tileLayer.getPreload());
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import CanvasVectorLayerRenderer from './VectorLayer.js';
|
|||||||
import EventType from '../../events/EventType.js';
|
import EventType from '../../events/EventType.js';
|
||||||
import ImageState from '../../ImageState.js';
|
import ImageState from '../../ImageState.js';
|
||||||
import {renderDeclutterItems} from '../../render.js';
|
import {renderDeclutterItems} from '../../render.js';
|
||||||
|
import {apply, compose, create} from '../../transform.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @classdesc
|
* @classdesc
|
||||||
@@ -36,6 +37,18 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
|||||||
*/
|
*/
|
||||||
this.layerImageRatio_ = layer.getImageRatio();
|
this.layerImageRatio_ = layer.getImageRatio();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {import("../../transform.js").Transform}
|
||||||
|
*/
|
||||||
|
this.coordinateToVectorPixelTransform_ = create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {import("../../transform.js").Transform}
|
||||||
|
*/
|
||||||
|
this.renderedPixelToCoordinateTransform_ = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,6 +59,22 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
|||||||
super.disposeInternal();
|
super.disposeInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
getFeatures(pixel) {
|
||||||
|
if (this.vectorRenderer_) {
|
||||||
|
const vectorPixel = apply(this.coordinateToVectorPixelTransform_,
|
||||||
|
apply(this.renderedPixelToCoordinateTransform_, pixel.slice()));
|
||||||
|
return this.vectorRenderer_.getFeatures(vectorPixel);
|
||||||
|
} else {
|
||||||
|
const promise = new Promise(function(resolve, reject) {
|
||||||
|
resolve([]);
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@@ -68,16 +97,15 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
|||||||
renderedExtent = renderedExtent.slice(0);
|
renderedExtent = renderedExtent.slice(0);
|
||||||
scaleFromCenter(renderedExtent, this.layerImageRatio_);
|
scaleFromCenter(renderedExtent, this.layerImageRatio_);
|
||||||
}
|
}
|
||||||
|
const width = getWidth(renderedExtent) / viewResolution;
|
||||||
|
const height = getHeight(renderedExtent) / viewResolution;
|
||||||
|
|
||||||
if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) {
|
if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) {
|
||||||
vectorRenderer.useContainer(null, null, 1);
|
vectorRenderer.useContainer(null, null, 1);
|
||||||
const context = vectorRenderer.context;
|
const context = vectorRenderer.context;
|
||||||
const imageFrameState = /** @type {import("../../PluggableMap.js").FrameState} */ (assign({}, frameState, {
|
const imageFrameState = /** @type {import("../../PluggableMap.js").FrameState} */ (assign({}, frameState, {
|
||||||
declutterItems: [],
|
declutterItems: [],
|
||||||
size: [
|
size: [width, height],
|
||||||
getWidth(renderedExtent) / viewResolution,
|
|
||||||
getHeight(renderedExtent) / viewResolution
|
|
||||||
],
|
|
||||||
viewState: /** @type {import("../../View.js").State} */ (assign({}, frameState.viewState, {
|
viewState: /** @type {import("../../View.js").State} */ (assign({}, frameState.viewState, {
|
||||||
rotation: 0
|
rotation: 0
|
||||||
}))
|
}))
|
||||||
@@ -102,7 +130,14 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
|||||||
const image = this.image_;
|
const image = this.image_;
|
||||||
const imageResolution = image.getResolution();
|
const imageResolution = image.getResolution();
|
||||||
const imagePixelRatio = image.getPixelRatio();
|
const imagePixelRatio = image.getPixelRatio();
|
||||||
this.renderedResolution = imageResolution * pixelRatio / imagePixelRatio;
|
const renderedResolution = imageResolution * pixelRatio / imagePixelRatio;
|
||||||
|
this.renderedResolution = renderedResolution;
|
||||||
|
this.renderedPixelToCoordinateTransform_ = frameState.pixelToCoordinateTransform.slice();
|
||||||
|
this.coordinateToVectorPixelTransform_ = compose(this.coordinateToVectorPixelTransform_,
|
||||||
|
width / 2, height / 2,
|
||||||
|
1 / renderedResolution, -1 / renderedResolution,
|
||||||
|
0,
|
||||||
|
-viewState.center[0], -viewState.center[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!this.image_;
|
return !!this.image_;
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
|||||||
import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
||||||
import CanvasLayerRenderer from './Layer.js';
|
import CanvasLayerRenderer from './Layer.js';
|
||||||
import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
||||||
import {toString as transformToString, makeScale, makeInverse} from '../../transform.js';
|
import {toString as transformToString, makeScale, makeInverse, apply} from '../../transform.js';
|
||||||
|
import {createHitDetectionImageData, hitDetect} from '../../render/canvas/hitdetect.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @classdesc
|
* @classdesc
|
||||||
@@ -28,6 +29,10 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
/** @private */
|
/** @private */
|
||||||
this.boundHandleStyleImageChange_ = this.handleStyleImageChange_.bind(this);
|
this.boundHandleStyleImageChange_ = this.handleStyleImageChange_.bind(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.animatingOrInteracting_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -35,6 +40,16 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
*/
|
*/
|
||||||
this.dirty_ = false;
|
this.dirty_ = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ImageData}
|
||||||
|
*/
|
||||||
|
this.hitDetectionImageData_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<import("../../Feature.js").default>}
|
||||||
|
*/
|
||||||
|
this.renderedFeatures_ = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -53,6 +68,24 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
*/
|
*/
|
||||||
this.renderedExtent_ = createEmpty();
|
this.renderedExtent_ = createEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.renderedRotation_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {import("../../coordinate").Coordinate}
|
||||||
|
*/
|
||||||
|
this.renderedCenter_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {import("../../proj/Projection").default}
|
||||||
|
*/
|
||||||
|
this.renderedProjection_ = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {function(import("../../Feature.js").default, import("../../Feature.js").default): number|null}
|
* @type {function(import("../../Feature.js").default, import("../../Feature.js").default): number|null}
|
||||||
@@ -124,6 +157,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
const extent = frameState.extent;
|
const extent = frameState.extent;
|
||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
|
const center = viewState.center;
|
||||||
|
const resolution = viewState.resolution;
|
||||||
const projection = viewState.projection;
|
const projection = viewState.projection;
|
||||||
const rotation = viewState.rotation;
|
const rotation = viewState.rotation;
|
||||||
const projectionExtent = projection.getExtent();
|
const projectionExtent = projection.getExtent();
|
||||||
@@ -143,7 +178,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const viewHints = frameState.viewHints;
|
const viewHints = frameState.viewHints;
|
||||||
const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
||||||
|
|
||||||
const transform = this.getRenderTransform(frameState, width, height, 0);
|
const transform = this.getRenderTransform(center, resolution, rotation, pixelRatio, width, height, 0);
|
||||||
const declutterReplays = this.getLayer().getDeclutter() ? {} : null;
|
const declutterReplays = this.getLayer().getDeclutter() ? {} : null;
|
||||||
replayGroup.execute(context, transform, rotation, snapToPixel, undefined, declutterReplays);
|
replayGroup.execute(context, transform, rotation, snapToPixel, undefined, declutterReplays);
|
||||||
|
|
||||||
@@ -155,7 +190,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
while (startX < projectionExtent[0]) {
|
while (startX < projectionExtent[0]) {
|
||||||
--world;
|
--world;
|
||||||
offsetX = worldWidth * world;
|
offsetX = worldWidth * world;
|
||||||
const transform = this.getRenderTransform(frameState, width, height, offsetX);
|
const transform = this.getRenderTransform(center, resolution, rotation, pixelRatio, width, height, offsetX);
|
||||||
replayGroup.execute(context, transform, rotation, snapToPixel, undefined, declutterReplays);
|
replayGroup.execute(context, transform, rotation, snapToPixel, undefined, declutterReplays);
|
||||||
startX += worldWidth;
|
startX += worldWidth;
|
||||||
}
|
}
|
||||||
@@ -164,7 +199,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
while (startX > projectionExtent[2]) {
|
while (startX > projectionExtent[2]) {
|
||||||
++world;
|
++world;
|
||||||
offsetX = worldWidth * world;
|
offsetX = worldWidth * world;
|
||||||
const transform = this.getRenderTransform(frameState, width, height, offsetX);
|
const transform = this.getRenderTransform(center, resolution, rotation, pixelRatio, width, height, offsetX);
|
||||||
replayGroup.execute(context, transform, rotation, snapToPixel, undefined, declutterReplays);
|
replayGroup.execute(context, transform, rotation, snapToPixel, undefined, declutterReplays);
|
||||||
startX -= worldWidth;
|
startX -= worldWidth;
|
||||||
}
|
}
|
||||||
@@ -190,6 +225,58 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
return this.container;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
getFeatures(pixel) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
if (!this.hitDetectionImageData_ && !this.animatingOrInteracting_) {
|
||||||
|
requestAnimationFrame(function() {
|
||||||
|
const size = [this.context.canvas.width, this.context.canvas.height];
|
||||||
|
apply(this.pixelTransform, size);
|
||||||
|
const center = this.renderedCenter_;
|
||||||
|
const resolution = this.renderedResolution_;
|
||||||
|
const rotation = this.renderedRotation_;
|
||||||
|
const projection = this.renderedProjection_;
|
||||||
|
const extent = this.renderedExtent_;
|
||||||
|
const layer = this.getLayer();
|
||||||
|
const transforms = [];
|
||||||
|
const width = size[0] / 2;
|
||||||
|
const height = size[1] / 2;
|
||||||
|
transforms.push(this.getRenderTransform(center, resolution, rotation, 0.5, width, height, 0).slice());
|
||||||
|
const source = layer.getSource();
|
||||||
|
const projectionExtent = projection.getExtent();
|
||||||
|
if (source.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
|
||||||
|
let startX = extent[0];
|
||||||
|
const worldWidth = getWidth(projectionExtent);
|
||||||
|
let world = 0;
|
||||||
|
let offsetX;
|
||||||
|
while (startX < projectionExtent[0]) {
|
||||||
|
--world;
|
||||||
|
offsetX = worldWidth * world;
|
||||||
|
transforms.push(this.getRenderTransform(center, resolution, rotation, 0.5, width, height, offsetX).slice());
|
||||||
|
startX += worldWidth;
|
||||||
|
}
|
||||||
|
world = 0;
|
||||||
|
startX = extent[2];
|
||||||
|
while (startX > projectionExtent[2]) {
|
||||||
|
++world;
|
||||||
|
offsetX = worldWidth * world;
|
||||||
|
transforms.push(this.getRenderTransform(center, resolution, rotation, 0.5, width, height, offsetX).slice());
|
||||||
|
startX -= worldWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hitDetectionImageData_ = createHitDetectionImageData(size, transforms,
|
||||||
|
this.renderedFeatures_, layer.getStyleFunction(), extent, resolution, rotation);
|
||||||
|
resolve(hitDetect(pixel, this.renderedFeatures_, this.hitDetectionImageData_));
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
resolve(hitDetect(pixel, this.renderedFeatures_, this.hitDetectionImageData_));
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@@ -253,8 +340,10 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
if (!this.dirty_ && (!updateWhileAnimating && animating) ||
|
if (!this.dirty_ && (!updateWhileAnimating && animating) ||
|
||||||
(!updateWhileInteracting && interacting)) {
|
(!updateWhileInteracting && interacting)) {
|
||||||
|
this.animatingOrInteracting_ = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
this.animatingOrInteracting_ = false;
|
||||||
|
|
||||||
const frameStateExtent = frameState.extent;
|
const frameStateExtent = frameState.extent;
|
||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
@@ -269,6 +358,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
vectorLayerRenderOrder = defaultRenderOrder;
|
vectorLayerRenderOrder = defaultRenderOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const center = viewState.center.slice();
|
||||||
const extent = buffer(frameStateExtent,
|
const extent = buffer(frameStateExtent,
|
||||||
vectorLayerRenderBuffer * resolution);
|
vectorLayerRenderBuffer * resolution);
|
||||||
const projectionExtent = viewState.projection.getExtent();
|
const projectionExtent = viewState.projection.getExtent();
|
||||||
@@ -284,6 +374,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const gutter = Math.max(getWidth(extent) / 2, worldWidth);
|
const gutter = Math.max(getWidth(extent) / 2, worldWidth);
|
||||||
extent[0] = projectionExtent[0] - gutter;
|
extent[0] = projectionExtent[0] - gutter;
|
||||||
extent[2] = projectionExtent[2] + gutter;
|
extent[2] = projectionExtent[2] + gutter;
|
||||||
|
const worldsAway = Math.floor((center[0] - projectionExtent[0]) / worldWidth);
|
||||||
|
center[0] -= (worldsAway * worldWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.dirty_ &&
|
if (!this.dirty_ &&
|
||||||
@@ -334,23 +426,15 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
const userExtent = toUserExtent(extent, projection);
|
const userExtent = toUserExtent(extent, projection);
|
||||||
if (vectorLayerRenderOrder) {
|
|
||||||
/** @type {Array<import("../../Feature.js").default>} */
|
/** @type {Array<import("../../Feature.js").default>} */
|
||||||
const features = [];
|
const features = vectorSource.getFeaturesInExtent(userExtent);
|
||||||
vectorSource.forEachFeatureInExtent(userExtent,
|
if (vectorLayerRenderOrder) {
|
||||||
/**
|
|
||||||
* @param {import("../../Feature.js").default} feature Feature.
|
|
||||||
*/
|
|
||||||
function(feature) {
|
|
||||||
features.push(feature);
|
|
||||||
});
|
|
||||||
features.sort(vectorLayerRenderOrder);
|
features.sort(vectorLayerRenderOrder);
|
||||||
|
}
|
||||||
for (let i = 0, ii = features.length; i < ii; ++i) {
|
for (let i = 0, ii = features.length; i < ii; ++i) {
|
||||||
render(features[i]);
|
render(features[i]);
|
||||||
}
|
}
|
||||||
} else {
|
this.renderedFeatures_ = features;
|
||||||
vectorSource.forEachFeatureInExtent(userExtent, render);
|
|
||||||
}
|
|
||||||
|
|
||||||
const replayGroupInstructions = replayGroup.finish();
|
const replayGroupInstructions = replayGroup.finish();
|
||||||
const executorGroup = new ExecutorGroup(extent, resolution,
|
const executorGroup = new ExecutorGroup(extent, resolution,
|
||||||
@@ -361,7 +445,11 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
this.renderedRevision_ = vectorLayerRevision;
|
this.renderedRevision_ = vectorLayerRevision;
|
||||||
this.renderedRenderOrder_ = vectorLayerRenderOrder;
|
this.renderedRenderOrder_ = vectorLayerRenderOrder;
|
||||||
this.renderedExtent_ = extent;
|
this.renderedExtent_ = extent;
|
||||||
|
this.renderedRotation_ = viewState.rotation;
|
||||||
|
this.renderedCenter_ = center;
|
||||||
|
this.renderedProjection_ = projection;
|
||||||
this.replayGroup_ = executorGroup;
|
this.replayGroup_ = executorGroup;
|
||||||
|
this.hitDetectionImageData_ = null;
|
||||||
|
|
||||||
this.replayGroupChanged = true;
|
this.replayGroupChanged = true;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import TileState from '../../TileState.js';
|
|||||||
import ViewHint from '../../ViewHint.js';
|
import ViewHint from '../../ViewHint.js';
|
||||||
import {listen, unlistenByKey} from '../../events.js';
|
import {listen, unlistenByKey} from '../../events.js';
|
||||||
import EventType from '../../events/EventType.js';
|
import EventType from '../../events/EventType.js';
|
||||||
import {buffer, containsCoordinate, equals, getIntersection, intersects} from '../../extent.js';
|
import {buffer, containsCoordinate, equals, getIntersection, intersects, containsExtent, getWidth, getTopLeft} from '../../extent.js';
|
||||||
import VectorTileRenderType from '../../layer/VectorTileRenderType.js';
|
import VectorTileRenderType from '../../layer/VectorTileRenderType.js';
|
||||||
import ReplayType from '../../render/canvas/BuilderType.js';
|
import ReplayType from '../../render/canvas/BuilderType.js';
|
||||||
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
||||||
import CanvasTileLayerRenderer from './TileLayer.js';
|
import CanvasTileLayerRenderer from './TileLayer.js';
|
||||||
|
import {toSize} from '../../size.js';
|
||||||
import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
||||||
import {
|
import {
|
||||||
apply as applyTransform,
|
apply as applyTransform,
|
||||||
@@ -25,6 +26,7 @@ import {
|
|||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
||||||
import {clear} from '../../obj.js';
|
import {clear} from '../../obj.js';
|
||||||
|
import {createHitDetectionImageData, hitDetect} from '../../render/canvas/hitdetect.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,6 +101,18 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
*/
|
*/
|
||||||
this.renderedLayerRevision_;
|
this.renderedLayerRevision_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {import("../../transform").Transform}
|
||||||
|
*/
|
||||||
|
this.renderedPixelToCoordinateTransform_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.renderedRotation_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {!Object<string, import("../../VectorRenderTile.js").default>}
|
* @type {!Object<string, import("../../VectorRenderTile.js").default>}
|
||||||
@@ -138,6 +152,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
const overlayContext = createCanvasContext2D();
|
const overlayContext = createCanvasContext2D();
|
||||||
const style = overlayContext.canvas.style;
|
const style = overlayContext.canvas.style;
|
||||||
style.position = 'absolute';
|
style.position = 'absolute';
|
||||||
|
style.left = '0';
|
||||||
style.transformOrigin = 'top left';
|
style.transformOrigin = 'top left';
|
||||||
this.overlayContext_ = overlayContext;
|
this.overlayContext_ = overlayContext;
|
||||||
this.overlayContextUid_ = getUid(overlayContext);
|
this.overlayContextUid_ = getUid(overlayContext);
|
||||||
@@ -152,10 +167,10 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {import("../../proj/Projection").default} projection Projection.
|
* @param {import("../../proj/Projection").default} projection Projection.
|
||||||
* @param {boolean} queue Queue tile for rendering.
|
* @param {boolean} queue Queue tile for rendering.
|
||||||
* @return {boolean} Tile needs to be rendered.
|
* @return {boolean|undefined} Tile needs to be rendered.
|
||||||
*/
|
*/
|
||||||
prepareTile(tile, pixelRatio, projection, queue) {
|
prepareTile(tile, pixelRatio, projection, queue) {
|
||||||
let render = false;
|
let render;
|
||||||
const tileUid = getUid(tile);
|
const tileUid = getUid(tile);
|
||||||
const state = tile.getState();
|
const state = tile.getState();
|
||||||
if (((state === TileState.LOADED && tile.hifi) ||
|
if (((state === TileState.LOADED && tile.hifi) ||
|
||||||
@@ -303,6 +318,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
if (renderOrder && renderOrder !== builderState.renderedRenderOrder) {
|
if (renderOrder && renderOrder !== builderState.renderedRenderOrder) {
|
||||||
features.sort(renderOrder);
|
features.sort(renderOrder);
|
||||||
}
|
}
|
||||||
|
sourceTile.hitDetectionImageData = null;
|
||||||
for (let i = 0, ii = features.length; i < ii; ++i) {
|
for (let i = 0, ii = features.length; i < ii; ++i) {
|
||||||
const feature = features[i];
|
const feature = features[i];
|
||||||
if (!bufferedExtent || intersects(bufferedExtent, feature.getGeometry().getExtent())) {
|
if (!bufferedExtent || intersects(bufferedExtent, feature.getGeometry().getExtent())) {
|
||||||
@@ -379,6 +395,73 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
getFeatures(pixel) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||||
|
const source = layer.getSource();
|
||||||
|
const projection = this.renderedProjection;
|
||||||
|
const projectionExtent = projection.getExtent();
|
||||||
|
const resolution = this.renderedResolution;
|
||||||
|
const pixelRatio = this.renderedPixelRatio;
|
||||||
|
const tileGrid = source.getTileGridForProjection(projection);
|
||||||
|
const sourceTileGrid = source.getTileGrid();
|
||||||
|
const coordinate = applyTransform(this.renderedPixelToCoordinateTransform_, pixel.slice());
|
||||||
|
const tileCoord = tileGrid.getTileCoordForCoordAndResolution(coordinate, resolution);
|
||||||
|
let sourceTile;
|
||||||
|
for (let i = 0, ii = this.renderedTiles.length; i < ii; ++i) {
|
||||||
|
if (tileCoord.toString() === this.renderedTiles[i].tileCoord.toString()) {
|
||||||
|
const tile = this.renderedTiles[i];
|
||||||
|
if (tile.getState() === TileState.LOADED && tile.hifi) {
|
||||||
|
const extent = tileGrid.getTileCoordExtent(tileCoord);
|
||||||
|
if (source.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
|
||||||
|
const worldWidth = getWidth(projectionExtent);
|
||||||
|
const worldsAway = Math.floor((coordinate[0] - projectionExtent[0]) / worldWidth);
|
||||||
|
coordinate[0] -= (worldsAway * worldWidth);
|
||||||
|
}
|
||||||
|
const sourceTiles = source.getSourceTiles(pixelRatio, projection, tile);
|
||||||
|
const sourceTileCoord = sourceTileGrid.getTileCoordForCoordAndResolution(coordinate, resolution);
|
||||||
|
for (let j = 0, jj = sourceTiles.length; j < jj; ++j) {
|
||||||
|
if (sourceTileCoord.toString() === sourceTiles[j].tileCoord.toString()) {
|
||||||
|
sourceTile = sourceTiles[j];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!sourceTile) {
|
||||||
|
resolve([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const corner = getTopLeft(tileGrid.getTileCoordExtent(sourceTile.tileCoord));
|
||||||
|
const tilePixel = [
|
||||||
|
(coordinate[0] - corner[0]) / resolution,
|
||||||
|
(corner[1] - coordinate[1]) / resolution
|
||||||
|
];
|
||||||
|
if (!sourceTile.hitDetectionImageData) {
|
||||||
|
const tileSize = toSize(sourceTileGrid.getTileSize(sourceTileGrid.getZForResolution(resolution)));
|
||||||
|
const size = [tileSize[0] / 2, tileSize[1] / 2];
|
||||||
|
const rotation = this.renderedRotation_;
|
||||||
|
const transforms = [
|
||||||
|
this.getRenderTransform(tileGrid.getTileCoordCenter(sourceTile.tileCoord),
|
||||||
|
resolution, 0, 0.5, size[0], size[1], 0)
|
||||||
|
];
|
||||||
|
requestAnimationFrame(function() {
|
||||||
|
sourceTile.hitDetectionImageData = createHitDetectionImageData(tileSize, transforms,
|
||||||
|
sourceTile.getFeatures(), layer.getStyleFunction(),
|
||||||
|
tileGrid.getTileCoordExtent(sourceTile.tileCoord), resolution, rotation);
|
||||||
|
resolve(hitDetect(tilePixel, sourceTile.getFeatures(), sourceTile.hitDetectionImageData));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve(hitDetect(tilePixel, sourceTile.getFeatures(), sourceTile.hitDetectionImageData));
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@@ -408,6 +491,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
this.renderQueuedTileImages_(hifi, frameState);
|
this.renderQueuedTileImages_(hifi, frameState);
|
||||||
|
|
||||||
super.renderFrame(frameState, target);
|
super.renderFrame(frameState, target);
|
||||||
|
this.renderedPixelToCoordinateTransform_ = frameState.pixelToCoordinateTransform.slice();
|
||||||
|
this.renderedRotation_ = frameState.viewState.rotation;
|
||||||
|
|
||||||
|
|
||||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||||
const renderMode = layer.getRenderMode();
|
const renderMode = layer.getRenderMode();
|
||||||
@@ -428,7 +514,10 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
const declutterReplays = layer.getDeclutter() ? {} : null;
|
const declutterReplays = layer.getDeclutter() ? {} : null;
|
||||||
const replayTypes = VECTOR_REPLAYS[renderMode];
|
const replayTypes = VECTOR_REPLAYS[renderMode];
|
||||||
const pixelRatio = frameState.pixelRatio;
|
const pixelRatio = frameState.pixelRatio;
|
||||||
const rotation = frameState.viewState.rotation;
|
const viewState = frameState.viewState;
|
||||||
|
const center = viewState.center;
|
||||||
|
const resolution = viewState.resolution;
|
||||||
|
const rotation = viewState.rotation;
|
||||||
const size = frameState.size;
|
const size = frameState.size;
|
||||||
|
|
||||||
// set forward and inverse pixel transforms
|
// set forward and inverse pixel transforms
|
||||||
@@ -462,7 +551,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
const tileCoord = tile.tileCoord;
|
const tileCoord = tile.tileCoord;
|
||||||
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
||||||
const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tileExtent[0];
|
const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tileExtent[0];
|
||||||
const transform = this.getRenderTransform(frameState, width, height, worldOffset);
|
const transform = this.getRenderTransform(center, resolution, rotation, pixelRatio, width, height, worldOffset);
|
||||||
const executorGroups = tile.executorGroups[getUid(layer)];
|
const executorGroups = tile.executorGroups[getUid(layer)];
|
||||||
let clipped = false;
|
let clipped = false;
|
||||||
for (let t = 0, tt = executorGroups.length; t < tt; ++t) {
|
for (let t = 0, tt = executorGroups.length; t < tt; ++t) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {AttributeType, DefaultUniform} from '../../webgl/Helper.js';
|
|||||||
import GeometryType from '../../geom/GeometryType.js';
|
import GeometryType from '../../geom/GeometryType.js';
|
||||||
import WebGLLayerRenderer, {colorDecodeId, colorEncodeId, WebGLWorkerMessageType} from './Layer.js';
|
import WebGLLayerRenderer, {colorDecodeId, colorEncodeId, WebGLWorkerMessageType} from './Layer.js';
|
||||||
import ViewHint from '../../ViewHint.js';
|
import ViewHint from '../../ViewHint.js';
|
||||||
import {createEmpty, equals} from '../../extent.js';
|
import {buffer, createEmpty, equals} from '../../extent.js';
|
||||||
import {
|
import {
|
||||||
apply as applyTransform,
|
apply as applyTransform,
|
||||||
create as createTransform,
|
create as createTransform,
|
||||||
@@ -18,6 +18,7 @@ import {create as createWebGLWorker} from '../../worker/webgl.js';
|
|||||||
import {getUid} from '../../util.js';
|
import {getUid} from '../../util.js';
|
||||||
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
|
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
|
||||||
import {assert} from '../../asserts.js';
|
import {assert} from '../../asserts.js';
|
||||||
|
import BaseVector from '../../layer/BaseVector.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} CustomAttribute A description of a custom attribute to be passed on to the GPU, with a value different
|
* @typedef {Object} CustomAttribute A description of a custom attribute to be passed on to the GPU, with a value different
|
||||||
@@ -132,6 +133,10 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
options.vertexShader
|
options.vertexShader
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.getShaderCompileErrors()) {
|
||||||
|
throw new Error(this.getShaderCompileErrors());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
* @private
|
* @private
|
||||||
@@ -143,6 +148,10 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
options.hitVertexShader
|
options.hitVertexShader
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.getShaderCompileErrors()) {
|
||||||
|
throw new Error(this.getShaderCompileErrors());
|
||||||
|
}
|
||||||
|
|
||||||
const customAttributes = options.attributes ?
|
const customAttributes = options.attributes ?
|
||||||
options.attributes.map(function(attribute) {
|
options.attributes.map(function(attribute) {
|
||||||
return {
|
return {
|
||||||
@@ -292,20 +301,22 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
const layer = this.getLayer();
|
const layer = this.getLayer();
|
||||||
const vectorSource = layer.getSource();
|
const vectorSource = layer.getSource();
|
||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
|
|
||||||
// the source has changed: clear the feature cache & reload features
|
|
||||||
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
|
|
||||||
if (sourceChanged) {
|
|
||||||
this.sourceRevision_ = vectorSource.getRevision();
|
|
||||||
|
|
||||||
const projection = viewState.projection;
|
|
||||||
const resolution = viewState.resolution;
|
|
||||||
vectorSource.loadFeatures([-Infinity, -Infinity, Infinity, Infinity], resolution, projection);
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewNotMoving = !frameState.viewHints[ViewHint.ANIMATING] && !frameState.viewHints[ViewHint.INTERACTING];
|
const viewNotMoving = !frameState.viewHints[ViewHint.ANIMATING] && !frameState.viewHints[ViewHint.INTERACTING];
|
||||||
const extentChanged = !equals(this.previousExtent_, frameState.extent);
|
const extentChanged = !equals(this.previousExtent_, frameState.extent);
|
||||||
if ((sourceChanged || extentChanged) && viewNotMoving) {
|
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
|
||||||
|
|
||||||
|
if (sourceChanged) {
|
||||||
|
this.sourceRevision_ = vectorSource.getRevision();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewNotMoving && (extentChanged || sourceChanged)) {
|
||||||
|
const projection = viewState.projection;
|
||||||
|
const resolution = viewState.resolution;
|
||||||
|
|
||||||
|
const renderBuffer = layer instanceof BaseVector ? layer.getRenderBuffer() : 0;
|
||||||
|
const extent = buffer(frameState.extent, renderBuffer * resolution);
|
||||||
|
vectorSource.loadFeatures(extent, resolution, projection);
|
||||||
|
|
||||||
this.rebuildBuffers_(frameState);
|
this.rebuildBuffers_(frameState);
|
||||||
this.previousExtent_ = frameState.extent.slice();
|
this.previousExtent_ = frameState.extent.slice();
|
||||||
}
|
}
|
||||||
@@ -401,7 +412,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */
|
/** @type {import('./Layer').WebGLWorkerGenerateBuffersMessage} */
|
||||||
const message = {
|
const message = {
|
||||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
||||||
renderInstructions: this.renderInstructions_.buffer,
|
renderInstructions: this.renderInstructions_.buffer,
|
||||||
@@ -412,7 +423,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.worker_.postMessage(message, [this.renderInstructions_.buffer]);
|
this.worker_.postMessage(message, [this.renderInstructions_.buffer]);
|
||||||
this.renderInstructions_ = null;
|
this.renderInstructions_ = null;
|
||||||
|
|
||||||
/** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */
|
/** @type {import('./Layer').WebGLWorkerGenerateBuffersMessage} */
|
||||||
if (this.hitDetectionEnabled_) {
|
if (this.hitDetectionEnabled_) {
|
||||||
const hitMessage = {
|
const hitMessage = {
|
||||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function toSize(size, opt_size) {
|
|||||||
if (opt_size === undefined) {
|
if (opt_size === undefined) {
|
||||||
opt_size = [size, size];
|
opt_size = [size, size];
|
||||||
} else {
|
} else {
|
||||||
opt_size[0] = opt_size[1] = /** @type {number} */ (size);
|
opt_size[0] = opt_size[1] = size;
|
||||||
}
|
}
|
||||||
return opt_size;
|
return opt_size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function quadKey(tileCoord) {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
const TOS_ATTRIBUTION = '<a class="ol-attribution-bing-tos" ' +
|
const TOS_ATTRIBUTION = '<a class="ol-attribution-bing-tos" ' +
|
||||||
'href="https://www.microsoft.com/maps/product/terms.html">' +
|
'href="https://www.microsoft.com/maps/product/terms.html" target="_blank">' +
|
||||||
'Terms of Use</a>';
|
'Terms of Use</a>';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import TileImage from './TileImage.js';
|
|||||||
* Higher values can increase reprojection performance, but decrease precision.
|
* Higher values can increase reprojection performance, but decrease precision.
|
||||||
* @property {Array<number>} [resolutions] Supported resolutions as given in IIIF 'scaleFactors'
|
* @property {Array<number>} [resolutions] Supported resolutions as given in IIIF 'scaleFactors'
|
||||||
* @property {import("../size.js").Size} size Size of the image [width, height].
|
* @property {import("../size.js").Size} size Size of the image [width, height].
|
||||||
* @property {import("../size.js").Size[]} [sizes] Supported scaled image sizes.
|
* @property {Array<import("../size.js").Size>} [sizes] Supported scaled image sizes.
|
||||||
* Content of the IIIF info.json 'sizes' property, but as array of Size objects.
|
* Content of the IIIF info.json 'sizes' property, but as array of Size objects.
|
||||||
* @property {import("./State.js").default} [state] Source state.
|
* @property {import("./State.js").default} [state] Source state.
|
||||||
* @property {Array<string>} [supports=[]] Supported IIIF region and size calculation
|
* @property {Array<string>} [supports=[]] Supported IIIF region and size calculation
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import XYZ from './XYZ.js';
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
export const ATTRIBUTION = '© ' +
|
export const ATTRIBUTION = '© ' +
|
||||||
'<a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> ' +
|
'<a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> ' +
|
||||||
'contributors.';
|
'contributors.';
|
||||||
|
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ export const ATTRIBUTION = '© ' +
|
|||||||
* @typedef {Object} Options
|
* @typedef {Object} Options
|
||||||
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
|
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
|
||||||
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
|
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
|
||||||
* @property {null|string} [crossOrigin] The `crossOrigin` attribute for loaded images. Note that
|
* @property {null|string} [crossOrigin='anonymous'] The `crossOrigin` attribute for loaded images. Note that
|
||||||
* you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.
|
* you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.
|
||||||
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
|
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
|
||||||
* @property {number} [maxZoom=19] Max zoom.
|
* @property {number} [maxZoom=19] Max zoom.
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import XYZ from './XYZ.js';
|
|||||||
* @type {Array<string>}
|
* @type {Array<string>}
|
||||||
*/
|
*/
|
||||||
const ATTRIBUTIONS = [
|
const ATTRIBUTIONS = [
|
||||||
'Map tiles by <a href="https://stamen.com/">Stamen Design</a>, ' +
|
'Map tiles by <a href="https://stamen.com/" target="_blank">Stamen Design</a>, ' +
|
||||||
'under <a href="https://creativecommons.org/licenses/by/3.0/">CC BY' +
|
'under <a href="https://creativecommons.org/licenses/by/3.0/" target="_blank">CC BY' +
|
||||||
' 3.0</a>.',
|
' 3.0</a>.',
|
||||||
OSM_ATTRIBUTION
|
OSM_ATTRIBUTION
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -675,14 +675,21 @@ class VectorSource extends Source {
|
|||||||
* all features intersecting the given extent in random order (so it may include
|
* all features intersecting the given extent in random order (so it may include
|
||||||
* features whose geometries do not intersect the extent).
|
* features whose geometries do not intersect the extent).
|
||||||
*
|
*
|
||||||
* This method is not available when the source is configured with
|
* When `useSpatialIndex` is set to false, this method will return all
|
||||||
* `useSpatialIndex` set to `false`.
|
* features.
|
||||||
|
*
|
||||||
* @param {import("../extent.js").Extent} extent Extent.
|
* @param {import("../extent.js").Extent} extent Extent.
|
||||||
* @return {Array<import("../Feature.js").default<Geometry>>} Features.
|
* @return {Array<import("../Feature.js").default<Geometry>>} Features.
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
getFeaturesInExtent(extent) {
|
getFeaturesInExtent(extent) {
|
||||||
|
if (this.featuresRtree_) {
|
||||||
return this.featuresRtree_.getInExtent(extent);
|
return this.featuresRtree_.getInExtent(extent);
|
||||||
|
} else if (this.featuresCollection_) {
|
||||||
|
return this.featuresCollection_.getArray();
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import {createXYZ, extentFromProjection, createForProjection} from '../tilegrid.
|
|||||||
import {buffer as bufferExtent, getIntersection, intersects} from '../extent.js';
|
import {buffer as bufferExtent, getIntersection, intersects} from '../extent.js';
|
||||||
import EventType from '../events/EventType.js';
|
import EventType from '../events/EventType.js';
|
||||||
import {loadFeaturesXhr} from '../featureloader.js';
|
import {loadFeaturesXhr} from '../featureloader.js';
|
||||||
import {isEmpty} from '../obj.js';
|
import {equals, remove} from '../array.js';
|
||||||
import {equals} from '../array.js';
|
import {listen, unlistenByKey} from '../events.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} Options
|
* @typedef {Object} Options
|
||||||
@@ -94,7 +94,7 @@ class VectorTile extends UrlTile {
|
|||||||
|
|
||||||
const tileGrid = options.tileGrid || createXYZ({
|
const tileGrid = options.tileGrid || createXYZ({
|
||||||
extent: extent,
|
extent: extent,
|
||||||
maxZoom: options.maxZoom || 22,
|
maxZoom: options.maxZoom !== undefined ? options.maxZoom : 22,
|
||||||
minZoom: options.minZoom,
|
minZoom: options.minZoom,
|
||||||
tileSize: options.tileSize || 512
|
tileSize: options.tileSize || 512
|
||||||
});
|
});
|
||||||
@@ -239,34 +239,27 @@ class VectorTile extends UrlTile {
|
|||||||
}
|
}
|
||||||
if (sourceTile.getState() !== TileState.EMPTY && tile.getState() === TileState.IDLE) {
|
if (sourceTile.getState() !== TileState.EMPTY && tile.getState() === TileState.IDLE) {
|
||||||
tile.loadingSourceTiles++;
|
tile.loadingSourceTiles++;
|
||||||
const onSourceTileChange = function() {
|
const key = listen(sourceTile, EventType.CHANGE, function() {
|
||||||
const state = sourceTile.getState();
|
const state = sourceTile.getState();
|
||||||
const sourceTileKey = sourceTile.getKey();
|
const sourceTileKey = sourceTile.getKey();
|
||||||
if (state === TileState.LOADED || state === TileState.ERROR) {
|
if (state === TileState.LOADED || state === TileState.ERROR) {
|
||||||
if (state === TileState.LOADED) {
|
if (state === TileState.LOADED) {
|
||||||
sourceTile.removeEventListener(EventType.CHANGE, onSourceTileChange);
|
remove(tile.sourceTileListenerKeys, key);
|
||||||
|
unlistenByKey(key);
|
||||||
tile.loadingSourceTiles--;
|
tile.loadingSourceTiles--;
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
tile.removeEventListener(EventType.CHANGE, onTileChange);
|
|
||||||
delete tile.errorSourceTileKeys[sourceTileKey];
|
delete tile.errorSourceTileKeys[sourceTileKey];
|
||||||
} else if (state === TileState.ERROR) {
|
} else if (state === TileState.ERROR) {
|
||||||
tile.errorSourceTileKeys[sourceTileKey] = true;
|
tile.errorSourceTileKeys[sourceTileKey] = true;
|
||||||
}
|
}
|
||||||
if (tile.loadingSourceTiles - Object.keys(tile.errorSourceTileKeys).length === 0) {
|
const errorTileCount = Object.keys(tile.errorSourceTileKeys).length;
|
||||||
tile.hifi = true;
|
if (tile.loadingSourceTiles - errorTileCount === 0) {
|
||||||
|
tile.hifi = errorTileCount === 0;
|
||||||
tile.sourceZ = sourceZ;
|
tile.sourceZ = sourceZ;
|
||||||
tile.setState(isEmpty(tile.errorSourceTileKeys) ? TileState.LOADED : TileState.ERROR);
|
tile.setState(TileState.LOADED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
const onTileChange = function() {
|
tile.sourceTileListenerKeys.push(key);
|
||||||
if (tile.getState() === TileState.ABORT) {
|
|
||||||
sourceTile.removeEventListener(EventType.CHANGE, onSourceTileChange);
|
|
||||||
tile.removeEventListener(EventType.CHANGE, onTileChange);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sourceTile.addEventListener(EventType.CHANGE, onSourceTileChange);
|
|
||||||
tile.addEventListener(EventType.CHANGE, onTileChange);
|
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
if (!covered) {
|
if (!covered) {
|
||||||
|
|||||||
@@ -4,8 +4,16 @@
|
|||||||
* @module ol/style/LiteralStyle
|
* @module ol/style/LiteralStyle
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import("./expressions.js").ExpressionValue} ExpressionValue
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} LiteralStyle
|
* @typedef {Object} LiteralStyle
|
||||||
|
* @property {ExpressionValue} [filter] Filter expression. If it resolves to a number strictly greater than 0, the
|
||||||
|
* point will be displayed. If undefined, all points will show.
|
||||||
|
* @property {Object<string, number>} [variables] Style variables; each variable must hold a number.
|
||||||
|
* Note: **this object is meant to be mutated**: changes to the values will immediately be visible on the rendered features
|
||||||
* @property {LiteralSymbolStyle} [symbol] Symbol representation.
|
* @property {LiteralSymbolStyle} [symbol] Symbol representation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -22,12 +30,12 @@ export const SymbolType = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} LiteralSymbolStyle
|
* @typedef {Object} LiteralSymbolStyle
|
||||||
* @property {number|Array.<number, number>} size Size, mandatory.
|
* @property {ExpressionValue|Array<ExpressionValue, ExpressionValue>} size Size, mandatory.
|
||||||
* @property {SymbolType} symbolType Symbol type to use, either a regular shape or an image.
|
* @property {SymbolType} symbolType Symbol type to use, either a regular shape or an image.
|
||||||
* @property {string} [src] Path to the image to be used for the symbol. Only required with `symbolType: 'image'`.
|
* @property {string} [src] Path to the image to be used for the symbol. Only required with `symbolType: 'image'`.
|
||||||
* @property {import("../color.js").Color|string} [color='#FFFFFF'] Color used for the representation (either fill, line or symbol).
|
* @property {import("../color.js").Color|Array<ExpressionValue>|string} [color='#FFFFFF'] Color used for the representation (either fill, line or symbol).
|
||||||
* @property {number} [opacity=1] Opacity.
|
* @property {ExpressionValue} [opacity=1] Opacity.
|
||||||
* @property {Array.<number, number>} [offset] Offset on X and Y axis for symbols. If not specified, the symbol will be centered.
|
* @property {Array<ExpressionValue, ExpressionValue>} [offset] Offset on X and Y axis for symbols. If not specified, the symbol will be centered.
|
||||||
* @property {Array.<number, number, number, number>} [textureCoord] Texture coordinates. If not specified, the whole texture will be used (range for 0 to 1 on both axes).
|
* @property {Array<ExpressionValue, ExpressionValue, ExpressionValue, ExpressionValue>} [textureCoord] Texture coordinates. If not specified, the whole texture will be used (range for 0 to 1 on both axes).
|
||||||
* @property {boolean} [rotateWithView=false] Specify whether the symbol must rotate with the view or stay upwards.
|
* @property {boolean} [rotateWithView=false] Specify whether the symbol must rotate with the view or stay upwards.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -98,8 +98,7 @@ class RegularShape extends ImageStyle {
|
|||||||
* @protected
|
* @protected
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.radius_ = /** @type {number} */ (options.radius !== undefined ?
|
this.radius_ = options.radius !== undefined ? options.radius : options.radius1;
|
||||||
options.radius : options.radius1);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
|||||||
656
src/ol/style/expressions.js
Normal file
656
src/ol/style/expressions.js
Normal file
@@ -0,0 +1,656 @@
|
|||||||
|
/**
|
||||||
|
* Operators and utilities used for style expressions
|
||||||
|
* @module ol/style/expressions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {asArray, isStringColor} from '../color.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base type used for literal style parameters; can be a number literal or the output of an operator,
|
||||||
|
* which in turns takes {@link ExpressionValue} arguments.
|
||||||
|
*
|
||||||
|
* The following operators can be used:
|
||||||
|
*
|
||||||
|
* * Reading operators:
|
||||||
|
* * `['get', 'attributeName']` fetches a feature attribute (it will be prefixed by `a_` in the shader)
|
||||||
|
* Note: those will be taken from the attributes provided to the renderer
|
||||||
|
* * `['var', 'varName']` fetches a value from the style variables, or 0 if undefined
|
||||||
|
* * `['time']` returns the time in seconds since the creation of the layer
|
||||||
|
*
|
||||||
|
* * Math operators:
|
||||||
|
* * `['*', value1, value1]` multiplies `value1` by `value2`
|
||||||
|
* * `['/', value1, value1]` divides `value1` by `value2`
|
||||||
|
* * `['+', value1, value1]` adds `value1` and `value2`
|
||||||
|
* * `['-', value1, value1]` subtracts `value2` from `value1`
|
||||||
|
* * `['clamp', value, low, high]` clamps `value` between `low` and `high`
|
||||||
|
* * `['%', value1, value1]` returns the result of `value1 % value2` (modulo)
|
||||||
|
* * `['^', value1, value1]` returns the value of `value1` raised to the `value2` power
|
||||||
|
*
|
||||||
|
* * Transform operators:
|
||||||
|
* * `['case', condition1, output1, ...conditionN, outputN, fallback]` selects the first output whose corresponding
|
||||||
|
* condition evaluates to `true`. If no match is found, returns the `fallback` value.
|
||||||
|
* All conditions should be `boolean`, output and fallback can be any kind.
|
||||||
|
* * `['match', input, match1, output1, ...matchN, outputN, fallback]` compares the `input` value against all
|
||||||
|
* provided `matchX` values, returning the output associated with the first valid match. If no match is found,
|
||||||
|
* returns the `fallback` value.
|
||||||
|
* `input` and `matchX` values must all be of the same type, and can be `number` or `string`. `outputX` and
|
||||||
|
* `fallback` values must be of the same type, and can be of any kind.
|
||||||
|
* * `['interpolate', interpolation, input, stop1, output1, ...stopN, outputN]` returns a value by interpolating between
|
||||||
|
* pairs of inputs and outputs; `interpolation` can either be `['linear']` or `['exponential', base]` where `base` is
|
||||||
|
* the rate of increase from stop A to stop B (i.e. power to which the interpolation ratio is raised); a value
|
||||||
|
* of 1 is equivalent to `['linear']`.
|
||||||
|
* `input` and `stopX` values must all be of type `number`. `outputX` values can be `number` or `color` values.
|
||||||
|
* Note: `input` will be clamped between `stop1` and `stopN`, meaning that all output values will be comprised
|
||||||
|
* between `output1` and `outputN`.
|
||||||
|
*
|
||||||
|
* * Logical operators:
|
||||||
|
* * `['<', value1, value2]` returns `true` if `value1` is strictly lower than value 2, or `false` otherwise.
|
||||||
|
* * `['<=', value1, value2]` returns `true` if `value1` is lower than or equals value 2, or `false` otherwise.
|
||||||
|
* * `['>', value1, value2]` returns `true` if `value1` is strictly greater than value 2, or `false` otherwise.
|
||||||
|
* * `['>=', value1, value2]` returns `true` if `value1` is greater than or equals value 2, or `false` otherwise.
|
||||||
|
* * `['==', value1, value2]` returns `true` if `value1` equals value 2, or `false` otherwise.
|
||||||
|
* * `['!=', value1, value2]` returns `true` if `value1` equals value 2, or `false` otherwise.
|
||||||
|
* * `['!', value1]` returns `false` if `value1` is `true` or greater than `0`, or `true` otherwise.
|
||||||
|
* * `['between', value1, value2, value3]` returns `true` if `value1` is contained between `value2` and `value3`
|
||||||
|
* (inclusively), or `false` otherwise.
|
||||||
|
*
|
||||||
|
* * Conversion operators:
|
||||||
|
* * `['array', value1, ...valueN]` creates a numerical array from `number` values; please note that the amount of
|
||||||
|
* values can currently only be 2, 3 or 4.
|
||||||
|
* * `['color', red, green, blue, alpha]` creates a `color` value from `number` values; the `alpha` parameter is
|
||||||
|
* optional; if not specified, it will be set to 1.
|
||||||
|
* Note: `red`, `green` and `blue` components must be values between 0 and 255; `alpha` between 0 and 1.
|
||||||
|
*
|
||||||
|
* Values can either be literals or another operator, as they will be evaluated recursively.
|
||||||
|
* Literal values can be of the following types:
|
||||||
|
* * `boolean`
|
||||||
|
* * `number`
|
||||||
|
* * `string`
|
||||||
|
* * {@link module:ol/color~Color}
|
||||||
|
*
|
||||||
|
* @typedef {Array<*>|import("../color.js").Color|string|number|boolean} ExpressionValue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possible inferred types from a given value or expression.
|
||||||
|
* Note: these are binary flags.
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
export const ValueTypes = {
|
||||||
|
NUMBER: 0b00001,
|
||||||
|
STRING: 0b00010,
|
||||||
|
COLOR: 0b00100,
|
||||||
|
BOOLEAN: 0b01000,
|
||||||
|
NUMBER_ARRAY: 0b10000,
|
||||||
|
ANY: 0b11111,
|
||||||
|
NONE: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An operator declaration must contain two methods: `getReturnType` which returns a type based on
|
||||||
|
* the operator arguments, and `toGlsl` which returns a GLSL-compatible string.
|
||||||
|
* Note: both methods can process arguments recursively.
|
||||||
|
* @typedef {Object} Operator
|
||||||
|
* @property {function(Array<ExpressionValue>): ValueTypes|number} getReturnType Returns one or several types
|
||||||
|
* @property {function(ParsingContext, Array<ExpressionValue>, ValueTypes=): string} toGlsl Returns a GLSL-compatible string
|
||||||
|
* Note: takes in an optional type hint as 3rd parameter
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator declarations
|
||||||
|
* @type {Object<string, Operator>}
|
||||||
|
*/
|
||||||
|
export const Operators = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the possible types for a given value (each type being a binary flag)
|
||||||
|
* To test a value use e.g. `getValueType(v) & ValueTypes.BOOLEAN`
|
||||||
|
* @param {ExpressionValue} value Value
|
||||||
|
* @returns {ValueTypes|number} Type or types inferred from the value
|
||||||
|
*/
|
||||||
|
export function getValueType(value) {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
}
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
if (isStringColor(value)) {
|
||||||
|
return ValueTypes.COLOR | ValueTypes.STRING;
|
||||||
|
}
|
||||||
|
return ValueTypes.STRING;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new Error(`Unhandled value type: ${JSON.stringify(value)}`);
|
||||||
|
}
|
||||||
|
const valueArr = /** @type {Array<*>} */(value);
|
||||||
|
const onlyNumbers = valueArr.every(function(v) {
|
||||||
|
return typeof v === 'number';
|
||||||
|
});
|
||||||
|
if (onlyNumbers) {
|
||||||
|
if (valueArr.length === 3 || valueArr.length === 4) {
|
||||||
|
return ValueTypes.COLOR | ValueTypes.NUMBER_ARRAY;
|
||||||
|
}
|
||||||
|
return ValueTypes.NUMBER_ARRAY;
|
||||||
|
}
|
||||||
|
if (typeof valueArr[0] !== 'string') {
|
||||||
|
throw new Error(`Expected an expression operator but received: ${JSON.stringify(valueArr)}`);
|
||||||
|
}
|
||||||
|
const operator = Operators[valueArr[0]];
|
||||||
|
if (operator === undefined) {
|
||||||
|
throw new Error(`Unrecognized expression operator: ${JSON.stringify(valueArr)}`);
|
||||||
|
}
|
||||||
|
return operator.getReturnType(valueArr.slice(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if only one value type is enabled in the input number.
|
||||||
|
* @param {ValueTypes|number} valueType Number containing value type binary flags
|
||||||
|
* @return {boolean} True if only one type flag is enabled, false if zero or multiple
|
||||||
|
*/
|
||||||
|
export function isTypeUnique(valueType) {
|
||||||
|
return Math.log2(valueType) % 1 === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context available during the parsing of an expression.
|
||||||
|
* @typedef {Object} ParsingContext
|
||||||
|
* @property {boolean} [inFragmentShader] If false, means the expression output should be made for a vertex shader
|
||||||
|
* @property {Array<string>} variables List of variables used in the expression; contains **unprefixed names**
|
||||||
|
* @property {Array<string>} attributes List of attributes used in the expression; contains **unprefixed names**
|
||||||
|
* @property {Object<string, number>} stringLiteralsMap This object maps all encountered string values to a number
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return the number as a float with a dot separator, which is required by GLSL.
|
||||||
|
* @param {number} v Numerical value.
|
||||||
|
* @returns {string} The value as string.
|
||||||
|
*/
|
||||||
|
export function numberToGlsl(v) {
|
||||||
|
const s = v.toString();
|
||||||
|
return s.indexOf('.') === -1 ? s + '.0' : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return the number array as a float with a dot separator, concatenated with ', '.
|
||||||
|
* @param {Array<number>} array Numerical values array.
|
||||||
|
* @returns {string} The array as a vector, e. g.: `vec3(1.0, 2.0, 3.0)`.
|
||||||
|
*/
|
||||||
|
export function arrayToGlsl(array) {
|
||||||
|
if (array.length < 2 || array.length > 4) {
|
||||||
|
throw new Error('`formatArray` can only output `vec2`, `vec3` or `vec4` arrays.');
|
||||||
|
}
|
||||||
|
return `vec${array.length}(${array.map(numberToGlsl).join(', ')})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will normalize and converts to string a `vec4` color array compatible with GLSL.
|
||||||
|
* @param {string|import("../color.js").Color} color Color either in string format or [r, g, b, a] array format,
|
||||||
|
* with RGB components in the 0..255 range and the alpha component in the 0..1 range.
|
||||||
|
* Note that the final array will always have 4 components.
|
||||||
|
* @returns {string} The color expressed in the `vec4(1.0, 1.0, 1.0, 1.0)` form.
|
||||||
|
*/
|
||||||
|
export function colorToGlsl(color) {
|
||||||
|
const array = asArray(color).slice();
|
||||||
|
if (array.length < 4) {
|
||||||
|
array.push(1);
|
||||||
|
}
|
||||||
|
return arrayToGlsl(
|
||||||
|
array.map(function(c, i) {
|
||||||
|
return i < 3 ? c / 255 : c;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a stable equivalent number for the string literal, for use in shaders. This number is then
|
||||||
|
* converted to be a GLSL-compatible string.
|
||||||
|
* @param {ParsingContext} context Parsing context
|
||||||
|
* @param {string} string String literal value
|
||||||
|
* @returns {string} GLSL-compatible string containing a number
|
||||||
|
*/
|
||||||
|
export function stringToGlsl(context, string) {
|
||||||
|
if (context.stringLiteralsMap[string] === undefined) {
|
||||||
|
context.stringLiteralsMap[string] = Object.keys(context.stringLiteralsMap).length;
|
||||||
|
}
|
||||||
|
return numberToGlsl(context.stringLiteralsMap[string]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively parses a style expression and outputs a GLSL-compatible string. Takes in a parsing context that
|
||||||
|
* will be read and modified during the parsing operation.
|
||||||
|
* @param {ParsingContext} context Parsing context
|
||||||
|
* @param {ExpressionValue} value Value
|
||||||
|
* @param {ValueTypes|number} [typeHint] Hint for the expected final type (can be several types combined)
|
||||||
|
* @returns {string} GLSL-compatible output
|
||||||
|
*/
|
||||||
|
export function expressionToGlsl(context, value, typeHint) {
|
||||||
|
// operator
|
||||||
|
if (Array.isArray(value) && typeof value[0] === 'string') {
|
||||||
|
const operator = Operators[value[0]];
|
||||||
|
if (operator === undefined) {
|
||||||
|
throw new Error(`Unrecognized expression operator: ${JSON.stringify(value)}`);
|
||||||
|
}
|
||||||
|
return operator.toGlsl(context, value.slice(1), typeHint);
|
||||||
|
} else if ((getValueType(value) & ValueTypes.NUMBER) > 0) {
|
||||||
|
return numberToGlsl(/** @type {number} */(value));
|
||||||
|
} else if ((getValueType(value) & ValueTypes.BOOLEAN) > 0) {
|
||||||
|
return value.toString();
|
||||||
|
} else if (
|
||||||
|
((getValueType(value) & ValueTypes.STRING) > 0) &&
|
||||||
|
(typeHint === undefined || typeHint == ValueTypes.STRING)
|
||||||
|
) {
|
||||||
|
return stringToGlsl(context, value.toString());
|
||||||
|
} else if (
|
||||||
|
((getValueType(value) & ValueTypes.COLOR) > 0) &&
|
||||||
|
(typeHint === undefined || typeHint == ValueTypes.COLOR)
|
||||||
|
) {
|
||||||
|
return colorToGlsl(/** @type {number[]|string} */(value));
|
||||||
|
} else if ((getValueType(value) & ValueTypes.NUMBER_ARRAY) > 0) {
|
||||||
|
return arrayToGlsl(/** @type {number[]} */(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertNumber(value) {
|
||||||
|
if (!(getValueType(value) & ValueTypes.NUMBER)) {
|
||||||
|
throw new Error(`A numeric value was expected, got ${JSON.stringify(value)} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertNumbers(values) {
|
||||||
|
for (let i = 0; i < values.length; i++) {
|
||||||
|
assertNumber(values[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertString(value) {
|
||||||
|
if (!(getValueType(value) & ValueTypes.STRING)) {
|
||||||
|
throw new Error(`A string value was expected, got ${JSON.stringify(value)} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertBoolean(value) {
|
||||||
|
if (!(getValueType(value) & ValueTypes.BOOLEAN)) {
|
||||||
|
throw new Error(`A boolean value was expected, got ${JSON.stringify(value)} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertArgsCount(args, count) {
|
||||||
|
if (args.length !== count) {
|
||||||
|
throw new Error(`Exactly ${count} arguments were expected, got ${args.length} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertArgsMinCount(args, count) {
|
||||||
|
if (args.length < count) {
|
||||||
|
throw new Error(`At least ${count} arguments were expected, got ${args.length} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertArgsMaxCount(args, count) {
|
||||||
|
if (args.length > count) {
|
||||||
|
throw new Error(`At most ${count} arguments were expected, got ${args.length} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertArgsEven(args) {
|
||||||
|
if (args.length % 2 !== 0) {
|
||||||
|
throw new Error(`An even amount of arguments was expected, got ${args} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertArgsOdd(args) {
|
||||||
|
if (args.length % 2 === 0) {
|
||||||
|
throw new Error(`An even amount of arguments was expected, got ${args} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertUniqueInferredType(args, types) {
|
||||||
|
if (!isTypeUnique(types)) {
|
||||||
|
throw new Error(`Could not infer only one type from the following expression: ${JSON.stringify(args)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Operators['get'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.ANY;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 1);
|
||||||
|
assertString(args[0]);
|
||||||
|
const value = args[0].toString();
|
||||||
|
if (context.attributes.indexOf(value) === -1) {
|
||||||
|
context.attributes.push(value);
|
||||||
|
}
|
||||||
|
const prefix = context.inFragmentShader ? 'v_' : 'a_';
|
||||||
|
return prefix + value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['var'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.ANY;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 1);
|
||||||
|
assertString(args[0]);
|
||||||
|
const value = args[0].toString();
|
||||||
|
if (context.variables.indexOf(value) === -1) {
|
||||||
|
context.variables.push(value);
|
||||||
|
}
|
||||||
|
return `u_${value}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['time'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 0);
|
||||||
|
return 'u_time';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['zoom'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 0);
|
||||||
|
return 'u_zoom';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['resolution'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 0);
|
||||||
|
return 'u_resolution';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Operators['*'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumbers(args);
|
||||||
|
return `(${expressionToGlsl(context, args[0])} * ${expressionToGlsl(context, args[1])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['/'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumbers(args);
|
||||||
|
return `(${expressionToGlsl(context, args[0])} / ${expressionToGlsl(context, args[1])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['+'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumbers(args);
|
||||||
|
return `(${expressionToGlsl(context, args[0])} + ${expressionToGlsl(context, args[1])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['-'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumbers(args);
|
||||||
|
return `(${expressionToGlsl(context, args[0])} - ${expressionToGlsl(context, args[1])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['clamp'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 3);
|
||||||
|
assertNumbers(args);
|
||||||
|
const min = expressionToGlsl(context, args[1]);
|
||||||
|
const max = expressionToGlsl(context, args[2]);
|
||||||
|
return `clamp(${expressionToGlsl(context, args[0])}, ${min}, ${max})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['%'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumbers(args);
|
||||||
|
return `mod(${expressionToGlsl(context, args[0])}, ${expressionToGlsl(context, args[1])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['^'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumbers(args);
|
||||||
|
return `pow(${expressionToGlsl(context, args[0])}, ${expressionToGlsl(context, args[1])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Operators['>'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumbers(args);
|
||||||
|
return `(${expressionToGlsl(context, args[0])} > ${expressionToGlsl(context, args[1])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['>='] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumbers(args);
|
||||||
|
return `(${expressionToGlsl(context, args[0])} >= ${expressionToGlsl(context, args[1])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['<'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumbers(args);
|
||||||
|
return `(${expressionToGlsl(context, args[0])} < ${expressionToGlsl(context, args[1])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['<='] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumbers(args);
|
||||||
|
return `(${expressionToGlsl(context, args[0])} <= ${expressionToGlsl(context, args[1])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getEqualOperator(operator) {
|
||||||
|
return {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
|
||||||
|
// find common type
|
||||||
|
let type = ValueTypes.ANY;
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
type = type & getValueType(args[i]);
|
||||||
|
}
|
||||||
|
if (type === 0) {
|
||||||
|
throw new Error(`All arguments should be of compatible type, got ${JSON.stringify(args)} instead`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `(${expressionToGlsl(context, args[0], type)} ${operator} ${expressionToGlsl(context, args[1], type)})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Operators['=='] = getEqualOperator('==');
|
||||||
|
Operators['!='] = getEqualOperator('!=');
|
||||||
|
|
||||||
|
Operators['!'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 1);
|
||||||
|
assertBoolean(args[0]);
|
||||||
|
return `(!${expressionToGlsl(context, args[0])})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['between'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsCount(args, 3);
|
||||||
|
assertNumbers(args);
|
||||||
|
const min = expressionToGlsl(context, args[1]);
|
||||||
|
const max = expressionToGlsl(context, args[2]);
|
||||||
|
const value = expressionToGlsl(context, args[0]);
|
||||||
|
return `(${value} >= ${min} && ${value} <= ${max})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Operators['array'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.NUMBER_ARRAY;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsMinCount(args, 2);
|
||||||
|
assertArgsMaxCount(args, 4);
|
||||||
|
assertNumbers(args);
|
||||||
|
const parsedArgs = args.map(function(val) {
|
||||||
|
return expressionToGlsl(context, val, ValueTypes.NUMBER);
|
||||||
|
});
|
||||||
|
return `vec${args.length}(${parsedArgs.join(', ')})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['color'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
return ValueTypes.COLOR;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args) {
|
||||||
|
assertArgsMinCount(args, 3);
|
||||||
|
assertArgsMaxCount(args, 4);
|
||||||
|
assertNumbers(args);
|
||||||
|
const array = /** @type {number[]} */(args);
|
||||||
|
if (args.length === 3) {
|
||||||
|
array.push(1);
|
||||||
|
}
|
||||||
|
const parsedArgs = args.map(function(val, i) {
|
||||||
|
return expressionToGlsl(context, val, ValueTypes.NUMBER) + (i < 3 ? ' / 255.0' : '');
|
||||||
|
});
|
||||||
|
return `vec${args.length}(${parsedArgs.join(', ')})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Operators['interpolate'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
let type = ValueTypes.COLOR | ValueTypes.NUMBER;
|
||||||
|
for (let i = 3; i < args.length; i += 2) {
|
||||||
|
type = type & getValueType(args[i]);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args, opt_typeHint) {
|
||||||
|
assertArgsEven(args);
|
||||||
|
assertArgsMinCount(args, 6);
|
||||||
|
|
||||||
|
// validate interpolation type
|
||||||
|
const type = args[0];
|
||||||
|
let interpolation;
|
||||||
|
switch (type[0]) {
|
||||||
|
case 'linear': interpolation = 1; break;
|
||||||
|
case 'exponential': interpolation = type[1]; break;
|
||||||
|
default: interpolation = null;
|
||||||
|
}
|
||||||
|
if (!interpolation) {
|
||||||
|
throw new Error(`Invalid interpolation type for "interpolate" operator, received: ${JSON.stringify(type)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute input/output types
|
||||||
|
const typeHint = opt_typeHint !== undefined ? opt_typeHint : ValueTypes.ANY;
|
||||||
|
const outputType = Operators['interpolate'].getReturnType(args) & typeHint;
|
||||||
|
assertUniqueInferredType(args, outputType);
|
||||||
|
|
||||||
|
const input = expressionToGlsl(context, args[1]);
|
||||||
|
let result = null;
|
||||||
|
for (let i = 2; i < args.length - 2; i += 2) {
|
||||||
|
const stop1 = expressionToGlsl(context, args[i]);
|
||||||
|
const output1 = expressionToGlsl(context, args[i + 1], outputType);
|
||||||
|
const stop2 = expressionToGlsl(context, args[i + 2]);
|
||||||
|
const output2 = expressionToGlsl(context, args[i + 3], outputType);
|
||||||
|
result = `mix(${result || output1}, ${output2}, pow(clamp((${input} - ${stop1}) / (${stop2} - ${stop1}), 0.0, 1.0), ${numberToGlsl(interpolation)}))`;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['match'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
let type = ValueTypes.ANY;
|
||||||
|
for (let i = 2; i < args.length; i += 2) {
|
||||||
|
type = type & getValueType(args[i]);
|
||||||
|
}
|
||||||
|
type = type & getValueType(args[args.length - 1]);
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args, opt_typeHint) {
|
||||||
|
assertArgsEven(args);
|
||||||
|
assertArgsMinCount(args, 4);
|
||||||
|
|
||||||
|
const typeHint = opt_typeHint !== undefined ? opt_typeHint : ValueTypes.ANY;
|
||||||
|
const outputType = Operators['match'].getReturnType(args) & typeHint;
|
||||||
|
assertUniqueInferredType(args, outputType);
|
||||||
|
|
||||||
|
const input = expressionToGlsl(context, args[0]);
|
||||||
|
const fallback = expressionToGlsl(context, args[args.length - 1], outputType);
|
||||||
|
let result = null;
|
||||||
|
for (let i = args.length - 3; i >= 1; i -= 2) {
|
||||||
|
const match = expressionToGlsl(context, args[i]);
|
||||||
|
const output = expressionToGlsl(context, args[i + 1], outputType);
|
||||||
|
result = `(${input} == ${match} ? ${output} : ${result || fallback})`;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Operators['case'] = {
|
||||||
|
getReturnType: function(args) {
|
||||||
|
let type = ValueTypes.ANY;
|
||||||
|
for (let i = 1; i < args.length; i += 2) {
|
||||||
|
type = type & getValueType(args[i]);
|
||||||
|
}
|
||||||
|
type = type & getValueType(args[args.length - 1]);
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, args, opt_typeHint) {
|
||||||
|
assertArgsOdd(args);
|
||||||
|
assertArgsMinCount(args, 3);
|
||||||
|
|
||||||
|
const typeHint = opt_typeHint !== undefined ? opt_typeHint : ValueTypes.ANY;
|
||||||
|
const outputType = Operators['case'].getReturnType(args) & typeHint;
|
||||||
|
assertUniqueInferredType(args, outputType);
|
||||||
|
for (let i = 0; i < args.length - 1; i += 2) {
|
||||||
|
assertBoolean(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallback = expressionToGlsl(context, args[args.length - 1], outputType);
|
||||||
|
let result = null;
|
||||||
|
for (let i = args.length - 3; i >= 0; i -= 2) {
|
||||||
|
const condition = expressionToGlsl(context, args[i]);
|
||||||
|
const output = expressionToGlsl(context, args[i + 1], outputType);
|
||||||
|
result = `(${condition} ? ${output} : ${result || fallback})`;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -88,12 +88,10 @@ export function createForExtent(extent, opt_maxZoom, opt_tileSize, opt_corner) {
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
export function createXYZ(opt_options) {
|
export function createXYZ(opt_options) {
|
||||||
/** @type {XYZOptions} */
|
|
||||||
const xyzOptions = opt_options || {};
|
const xyzOptions = opt_options || {};
|
||||||
|
|
||||||
const extent = xyzOptions.extent || getProjection('EPSG:3857').getExtent();
|
const extent = xyzOptions.extent || getProjection('EPSG:3857').getExtent();
|
||||||
|
|
||||||
/** @type {import("./tilegrid/TileGrid.js").Options} */
|
|
||||||
const gridOptions = {
|
const gridOptions = {
|
||||||
extent: extent,
|
extent: extent,
|
||||||
minZoom: xyzOptions.minZoom,
|
minZoom: xyzOptions.minZoom,
|
||||||
|
|||||||
@@ -42,7 +42,10 @@ export const ShaderType = {
|
|||||||
export const DefaultUniform = {
|
export const DefaultUniform = {
|
||||||
PROJECTION_MATRIX: 'u_projectionMatrix',
|
PROJECTION_MATRIX: 'u_projectionMatrix',
|
||||||
OFFSET_SCALE_MATRIX: 'u_offsetScaleMatrix',
|
OFFSET_SCALE_MATRIX: 'u_offsetScaleMatrix',
|
||||||
OFFSET_ROTATION_MATRIX: 'u_offsetRotateMatrix'
|
OFFSET_ROTATION_MATRIX: 'u_offsetRotateMatrix',
|
||||||
|
TIME: 'u_time',
|
||||||
|
ZOOM: 'u_zoom',
|
||||||
|
RESOLUTION: 'u_resolution'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,6 +250,7 @@ class WebGLHelper extends Disposable {
|
|||||||
*/
|
*/
|
||||||
this.canvas_ = document.createElement('canvas');
|
this.canvas_ = document.createElement('canvas');
|
||||||
this.canvas_.style.position = 'absolute';
|
this.canvas_.style.position = 'absolute';
|
||||||
|
this.canvas_.style.left = '0';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -354,6 +358,12 @@ class WebGLHelper extends Disposable {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.shaderCompileErrors_ = null;
|
this.shaderCompileErrors_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.startTime_ = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -547,6 +557,10 @@ class WebGLHelper extends Disposable {
|
|||||||
|
|
||||||
this.setUniformMatrixValue(DefaultUniform.OFFSET_SCALE_MATRIX, fromTransform(this.tmpMat4_, offsetScaleMatrix));
|
this.setUniformMatrixValue(DefaultUniform.OFFSET_SCALE_MATRIX, fromTransform(this.tmpMat4_, offsetScaleMatrix));
|
||||||
this.setUniformMatrixValue(DefaultUniform.OFFSET_ROTATION_MATRIX, fromTransform(this.tmpMat4_, offsetRotateMatrix));
|
this.setUniformMatrixValue(DefaultUniform.OFFSET_ROTATION_MATRIX, fromTransform(this.tmpMat4_, offsetRotateMatrix));
|
||||||
|
|
||||||
|
this.setUniformFloatValue(DefaultUniform.TIME, (Date.now() - this.startTime_) * 0.001);
|
||||||
|
this.setUniformFloatValue(DefaultUniform.ZOOM, frameState.viewState.zoom);
|
||||||
|
this.setUniformFloatValue(DefaultUniform.RESOLUTION, frameState.viewState.resolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,41 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Utilities for generating shaders from literal style objects
|
* Classes and utilities for generating shaders from literal style objects
|
||||||
* @module ol/webgl/ShaderBuilder
|
* @module ol/webgl/ShaderBuilder
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {asArray} from '../color.js';
|
import {expressionToGlsl, stringToGlsl, ValueTypes} from '../style/expressions.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* Will return the number as a float with a dot separator, which is required by GLSL.
|
|
||||||
* @param {number} v Numerical value.
|
|
||||||
* @returns {string} The value as string.
|
|
||||||
*/
|
|
||||||
export function formatNumber(v) {
|
|
||||||
const s = v.toString();
|
|
||||||
return s.indexOf('.') === -1 ? s + '.0' : s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will return the number array as a float with a dot separator, concatenated with ', '.
|
|
||||||
* @param {Array<number>} array Numerical values array.
|
|
||||||
* @returns {string} The array as string, e. g.: `1.0, 2.0, 3.0`.
|
|
||||||
*/
|
|
||||||
export function formatArray(array) {
|
|
||||||
return array.map(formatNumber).join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will normalize and converts to string a color array compatible with GLSL.
|
|
||||||
* @param {Array<number>} colorArray Color in [r, g, b, a] array form, with RGB components in the
|
|
||||||
* 0..255 range and the alpha component in the 0..1 range. Note that if the A component is
|
|
||||||
* missing, only 3 values will be output.
|
|
||||||
* @returns {string} The color components concatenated in `1.0, 1.0, 1.0, 1.0` form.
|
|
||||||
*/
|
|
||||||
export function formatColor(colorArray) {
|
|
||||||
return colorArray.map(function(c, i) {
|
|
||||||
return i < 3 ? c / 255 : c;
|
|
||||||
}).map(formatNumber).join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} VaryingDescription
|
* @typedef {Object} VaryingDescription
|
||||||
@@ -46,47 +14,269 @@ export function formatColor(colorArray) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} ShaderParameters
|
* @classdesc
|
||||||
* @property {Array<string>} [uniforms] Uniforms; these will be declared in the header (should include the type).
|
* This class implements a classic builder pattern for generating many different types of shaders.
|
||||||
* @property {Array<string>} [attributes] Attributes; these will be declared in the header (should include the type).
|
* Methods can be chained, e. g.:
|
||||||
* @property {Array<VaryingDescription>} [varyings] Varyings with a name, a type and an expression.
|
*
|
||||||
* @property {string} sizeExpression This will be assigned to a `vec2 size` variable.
|
* ```js
|
||||||
* @property {string} offsetExpression This will be assigned to a `vec2 offset` variable.
|
* const shader = new ShaderBuilder()
|
||||||
* @property {string} colorExpression This will be the value assigned to gl_FragColor
|
* .addVarying('v_width', 'float', 'a_width')
|
||||||
* @property {string} texCoordExpression This will be the value assigned to the `vec4 v_texCoord` varying.
|
* .addUniform('u_time')
|
||||||
* @property {boolean} [rotateWithView=false] Whether symbols should rotate with view
|
* .setColorExpression('...')
|
||||||
|
* .setSizeExpression('...')
|
||||||
|
* .outputSymbolFragmentShader();
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
|
export class ShaderBuilder {
|
||||||
|
constructor() {
|
||||||
|
/**
|
||||||
|
* Uniforms; these will be declared in the header (should include the type).
|
||||||
|
* @type {Array<string>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.uniforms = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a symbol vertex shader from a set of parameters,
|
* Attributes; these will be declared in the header (should include the type).
|
||||||
|
* @type {Array<string>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.attributes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Varyings with a name, a type and an expression.
|
||||||
|
* @type {Array<VaryingDescription>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.varyings = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.sizeExpression = 'vec2(1.0)';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.offsetExpression = 'vec2(0.0)';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.colorExpression = 'vec4(1.0)';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.texCoordExpression = 'vec4(0.0, 0.0, 1.0, 1.0)';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.discardExpression = 'false';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.rotateWithView = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a uniform accessible in both fragment and vertex shaders.
|
||||||
|
* The given name should include a type, such as `sampler2D u_texture`.
|
||||||
|
* @param {string} name Uniform name
|
||||||
|
* @return {ShaderBuilder} the builder object
|
||||||
|
*/
|
||||||
|
addUniform(name) {
|
||||||
|
this.uniforms.push(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute accessible in the vertex shader, read from the geometry buffer.
|
||||||
|
* The given name should include a type, such as `vec2 a_position`.
|
||||||
|
* @param {string} name Attribute name
|
||||||
|
* @return {ShaderBuilder} the builder object
|
||||||
|
*/
|
||||||
|
addAttribute(name) {
|
||||||
|
this.attributes.push(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a varying defined in the vertex shader and accessible from the fragment shader.
|
||||||
|
* The type and expression of the varying have to be specified separately.
|
||||||
|
* @param {string} name Varying name
|
||||||
|
* @param {'float'|'vec2'|'vec3'|'vec4'} type Type
|
||||||
|
* @param {string} expression Expression used to assign a value to the varying.
|
||||||
|
* @return {ShaderBuilder} the builder object
|
||||||
|
*/
|
||||||
|
addVarying(name, type, expression) {
|
||||||
|
this.varyings.push({
|
||||||
|
name: name,
|
||||||
|
type: type,
|
||||||
|
expression: expression
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an expression to compute the size of the shape.
|
||||||
|
* This expression can use all the uniforms and attributes available
|
||||||
|
* in the vertex shader, and should evaluate to a `vec2` value.
|
||||||
|
* @param {string} expression Size expression
|
||||||
|
* @return {ShaderBuilder} the builder object
|
||||||
|
*/
|
||||||
|
setSizeExpression(expression) {
|
||||||
|
this.sizeExpression = expression;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an expression to compute the offset of the symbol from the point center.
|
||||||
|
* This expression can use all the uniforms and attributes available
|
||||||
|
* in the vertex shader, and should evaluate to a `vec2` value.
|
||||||
|
* Note: will only be used for point geometry shaders.
|
||||||
|
* @param {string} expression Offset expression
|
||||||
|
* @return {ShaderBuilder} the builder object
|
||||||
|
*/
|
||||||
|
setSymbolOffsetExpression(expression) {
|
||||||
|
this.offsetExpression = expression;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an expression to compute the color of the shape.
|
||||||
|
* This expression can use all the uniforms, varyings and attributes available
|
||||||
|
* in the fragment shader, and should evaluate to a `vec4` value.
|
||||||
|
* @param {string} expression Color expression
|
||||||
|
* @return {ShaderBuilder} the builder object
|
||||||
|
*/
|
||||||
|
setColorExpression(expression) {
|
||||||
|
this.colorExpression = expression;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an expression to compute the texture coordinates of the vertices.
|
||||||
|
* This expression can use all the uniforms and attributes available
|
||||||
|
* in the vertex shader, and should evaluate to a `vec4` value.
|
||||||
|
* @param {string} expression Texture coordinate expression
|
||||||
|
* @return {ShaderBuilder} the builder object
|
||||||
|
*/
|
||||||
|
setTextureCoordinateExpression(expression) {
|
||||||
|
this.texCoordExpression = expression;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an expression to determine whether a fragment (pixel) should be discarded,
|
||||||
|
* i.e. not drawn at all.
|
||||||
|
* This expression can use all the uniforms, varyings and attributes available
|
||||||
|
* in the fragment shader, and should evaluate to a `bool` value (it will be
|
||||||
|
* used in an `if` statement)
|
||||||
|
* @param {string} expression Fragment discard expression
|
||||||
|
* @return {ShaderBuilder} the builder object
|
||||||
|
*/
|
||||||
|
setFragmentDiscardExpression(expression) {
|
||||||
|
this.discardExpression = expression;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the symbols should rotate with the view or stay aligned with the map.
|
||||||
|
* Note: will only be used for point geometry shaders.
|
||||||
|
* @param {boolean} rotateWithView Rotate with view
|
||||||
|
* @return {ShaderBuilder} the builder object
|
||||||
|
*/
|
||||||
|
setSymbolRotateWithView(rotateWithView) {
|
||||||
|
this.rotateWithView = rotateWithView;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} Previously set size expression
|
||||||
|
*/
|
||||||
|
getSizeExpression() {
|
||||||
|
return this.sizeExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} Previously set symbol offset expression
|
||||||
|
*/
|
||||||
|
getOffsetExpression() {
|
||||||
|
return this.offsetExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} Previously set color expression
|
||||||
|
*/
|
||||||
|
getColorExpression() {
|
||||||
|
return this.colorExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} Previously set texture coordinate expression
|
||||||
|
*/
|
||||||
|
getTextureCoordinateExpression() {
|
||||||
|
return this.texCoordExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} Previously set fragment discard expression
|
||||||
|
*/
|
||||||
|
getFragmentDiscardExpression() {
|
||||||
|
return this.discardExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a symbol vertex shader from the builder parameters,
|
||||||
* intended to be used on point geometries.
|
* intended to be used on point geometries.
|
||||||
*
|
*
|
||||||
* Three uniforms are hardcoded in all shaders: `u_projectionMatrix`, `u_offsetScaleMatrix` and
|
* Three uniforms are hardcoded in all shaders: `u_projectionMatrix`, `u_offsetScaleMatrix`,
|
||||||
* `u_offsetRotateMatrix`.
|
* `u_offsetRotateMatrix`, `u_time`.
|
||||||
*
|
*
|
||||||
* The following attributes are hardcoded and expected to be present in the vertex buffers:
|
* The following attributes are hardcoded and expected to be present in the vertex buffers:
|
||||||
* `vec2 a_position`, `float a_index` (being the index of the vertex in the quad, 0 to 3).
|
* `vec2 a_position`, `float a_index` (being the index of the vertex in the quad, 0 to 3).
|
||||||
*
|
*
|
||||||
* The following varyings are hardcoded and gives the coordinate of the pixel both in the quad on the texture:
|
* The following varyings are hardcoded and gives the coordinate of the pixel both in the quad and on the texture:
|
||||||
* `vec2 v_quadCoord`, `vec2 v_texCoord`
|
* `vec2 v_quadCoord`, `vec2 v_texCoord`
|
||||||
*
|
*
|
||||||
* @param {ShaderParameters} parameters Parameters for the shader.
|
* @param {boolean} [forHitDetection] If true, the shader will be modified to include hit detection variables
|
||||||
|
* (namely, hit color with encoded feature id).
|
||||||
* @returns {string} The full shader as a string.
|
* @returns {string} The full shader as a string.
|
||||||
*/
|
*/
|
||||||
export function getSymbolVertexShader(parameters) {
|
getSymbolVertexShader(forHitDetection) {
|
||||||
const offsetMatrix = parameters.rotateWithView ?
|
const offsetMatrix = this.rotateWithView ?
|
||||||
'u_offsetScaleMatrix * u_offsetRotateMatrix' :
|
'u_offsetScaleMatrix * u_offsetRotateMatrix' :
|
||||||
'u_offsetScaleMatrix';
|
'u_offsetScaleMatrix';
|
||||||
|
|
||||||
const uniforms = parameters.uniforms || [];
|
let attributes = this.attributes;
|
||||||
const attributes = parameters.attributes || [];
|
let varyings = this.varyings;
|
||||||
const varyings = parameters.varyings || [];
|
|
||||||
|
|
||||||
const body = `precision mediump float;
|
if (forHitDetection) {
|
||||||
|
attributes = attributes.concat('vec4 a_hitColor');
|
||||||
|
varyings = varyings.concat({
|
||||||
|
name: 'v_hitColor',
|
||||||
|
type: 'vec4',
|
||||||
|
expression: 'a_hitColor'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return `precision mediump float;
|
||||||
uniform mat4 u_projectionMatrix;
|
uniform mat4 u_projectionMatrix;
|
||||||
uniform mat4 u_offsetScaleMatrix;
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
uniform mat4 u_offsetRotateMatrix;
|
uniform mat4 u_offsetRotateMatrix;
|
||||||
${uniforms.map(function(uniform) {
|
uniform float u_time;
|
||||||
|
uniform float u_zoom;
|
||||||
|
uniform float u_resolution;
|
||||||
|
${this.uniforms.map(function(uniform) {
|
||||||
return 'uniform ' + uniform + ';';
|
return 'uniform ' + uniform + ';';
|
||||||
}).join('\n')}
|
}).join('\n')}
|
||||||
attribute vec2 a_position;
|
attribute vec2 a_position;
|
||||||
@@ -101,15 +291,15 @@ ${varyings.map(function(varying) {
|
|||||||
}).join('\n')}
|
}).join('\n')}
|
||||||
void main(void) {
|
void main(void) {
|
||||||
mat4 offsetMatrix = ${offsetMatrix};
|
mat4 offsetMatrix = ${offsetMatrix};
|
||||||
vec2 size = ${parameters.sizeExpression};
|
vec2 size = ${this.sizeExpression};
|
||||||
vec2 offset = ${parameters.offsetExpression};
|
vec2 offset = ${this.offsetExpression};
|
||||||
float offsetX = a_index == 0.0 || a_index == 3.0 ? offset.x - size.x / 2.0 : offset.x + size.x / 2.0;
|
float offsetX = a_index == 0.0 || a_index == 3.0 ? offset.x - size.x / 2.0 : offset.x + size.x / 2.0;
|
||||||
float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0;
|
float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0;
|
||||||
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
|
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
|
||||||
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
||||||
vec4 texCoord = ${parameters.texCoordExpression};
|
vec4 texCoord = ${this.texCoordExpression};
|
||||||
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.p;
|
||||||
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.q;
|
||||||
v_texCoord = vec2(u, v);
|
v_texCoord = vec2(u, v);
|
||||||
u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;
|
u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
v = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0;
|
v = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
@@ -118,25 +308,38 @@ ${varyings.map(function(varying) {
|
|||||||
return ' ' + varying.name + ' = ' + varying.expression + ';';
|
return ' ' + varying.name + ' = ' + varying.expression + ';';
|
||||||
}).join('\n')}
|
}).join('\n')}
|
||||||
}`;
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
return body;
|
/**
|
||||||
}
|
* Generates a symbol fragment shader from the builder parameters,
|
||||||
|
* intended to be used on point geometries.
|
||||||
/**
|
|
||||||
* Generates a symbol fragment shader intended to be used on point geometries.
|
|
||||||
*
|
*
|
||||||
* Expected the following varyings to be transmitted by the vertex shader:
|
* Expects the following varyings to be transmitted by the vertex shader:
|
||||||
* `vec2 v_texCoord`
|
* `vec2 v_quadCoord`, `vec2 v_texCoord`
|
||||||
*
|
*
|
||||||
* @param {ShaderParameters} parameters Parameters for the shader.
|
* @param {boolean} [forHitDetection] If true, the shader will be modified to include hit detection variables
|
||||||
|
* (namely, hit color with encoded feature id).
|
||||||
* @returns {string} The full shader as a string.
|
* @returns {string} The full shader as a string.
|
||||||
*/
|
*/
|
||||||
export function getSymbolFragmentShader(parameters) {
|
getSymbolFragmentShader(forHitDetection) {
|
||||||
const uniforms = parameters.uniforms || [];
|
const hitDetectionBypass = forHitDetection ?
|
||||||
const varyings = parameters.varyings || [];
|
' if (gl_FragColor.a < 0.1) { discard; } gl_FragColor = v_hitColor;' : '';
|
||||||
|
|
||||||
const body = `precision mediump float;
|
let varyings = this.varyings;
|
||||||
${uniforms.map(function(uniform) {
|
|
||||||
|
if (forHitDetection) {
|
||||||
|
varyings = varyings.concat({
|
||||||
|
name: 'v_hitColor',
|
||||||
|
type: 'vec4',
|
||||||
|
expression: 'a_hitColor'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return `precision mediump float;
|
||||||
|
uniform float u_time;
|
||||||
|
uniform float u_zoom;
|
||||||
|
uniform float u_resolution;
|
||||||
|
${this.uniforms.map(function(uniform) {
|
||||||
return 'uniform ' + uniform + ';';
|
return 'uniform ' + uniform + ';';
|
||||||
}).join('\n')}
|
}).join('\n')}
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
@@ -145,110 +348,68 @@ ${varyings.map(function(varying) {
|
|||||||
return 'varying ' + varying.type + ' ' + varying.name + ';';
|
return 'varying ' + varying.type + ' ' + varying.name + ';';
|
||||||
}).join('\n')}
|
}).join('\n')}
|
||||||
void main(void) {
|
void main(void) {
|
||||||
gl_FragColor = ${parameters.colorExpression};
|
if (${this.discardExpression}) { discard; }
|
||||||
|
gl_FragColor = ${this.colorExpression};
|
||||||
gl_FragColor.rgb *= gl_FragColor.a;
|
gl_FragColor.rgb *= gl_FragColor.a;
|
||||||
|
${hitDetectionBypass}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base type for values fed to operators; can be a number literal or the output of another operator
|
|
||||||
* @typedef {Array<*>|number} OperatorValue
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the provided expressions and produces a GLSL-compatible assignment string, such as:
|
|
||||||
* `['add', ['*', ['get', 'size'], 0.001], 12] => '(a_size * (0.001)) + (12.0)'
|
|
||||||
*
|
|
||||||
* The following operators can be used:
|
|
||||||
* * `['get', 'attributeName']` fetches a feature attribute (it will be prefixed by `a_` in the shader)
|
|
||||||
* * `['*', value1, value1]` multiplies value1 by value2
|
|
||||||
* * `['+', value1, value1]` adds value1 and value2
|
|
||||||
* * `['clamp', value1, value2, value3]` clamps value1 between values2 and value3
|
|
||||||
* * `['stretch', value1, value2, value3, value4, value5]` maps value1 from [value2, value3] range to
|
|
||||||
* [value4, value5] range, clamping values along the way
|
|
||||||
*
|
|
||||||
* Values can either be literals (numbers) or another operator, as they will be evaluated recursively.
|
|
||||||
*
|
|
||||||
* Also takes in an array where new attributes will be pushed, so that the user of the `parse` function
|
|
||||||
* knows which attributes are expected to be available at evaluation time.
|
|
||||||
*
|
|
||||||
* A prefix must be specified so that the attributes can either be written as `a_name` or `v_name` in
|
|
||||||
* the final assignment string.
|
|
||||||
*
|
|
||||||
* @param {OperatorValue} value Either literal or an operator.
|
|
||||||
* @param {Array<string>} attributes Array containing the attribute names prefixed with `a_`; it
|
|
||||||
* it passed along recursively
|
|
||||||
* @param {string} attributePrefix Prefix added to attribute names in the final output (typically `a_` or `v_`).
|
|
||||||
* @returns {string} Assignment string.
|
|
||||||
*/
|
|
||||||
export function parse(value, attributes, attributePrefix) {
|
|
||||||
const v = value;
|
|
||||||
function p(value) {
|
|
||||||
return parse(value, attributes, attributePrefix);
|
|
||||||
}
|
|
||||||
if (Array.isArray(v)) {
|
|
||||||
switch (v[0]) {
|
|
||||||
case 'get':
|
|
||||||
if (attributes.indexOf(v[1]) === -1) {
|
|
||||||
attributes.push(v[1]);
|
|
||||||
}
|
|
||||||
return attributePrefix + v[1];
|
|
||||||
case '*': return `(${p(v[1])} * ${p(v[2])})`;
|
|
||||||
case '+': return `(${p(v[1])} + ${p(v[2])})`;
|
|
||||||
case 'clamp': return `clamp(${p(v[1])}, ${p(v[2])}, ${p(v[3])})`;
|
|
||||||
case 'stretch': return `(clamp(${p(v[1])}, ${p(v[2])}, ${p(v[3])}) * ((${p(v[5])} - ${p(v[4])}) / (${p(v[3])} - ${p(v[2])})) + ${p(v[4])})`;
|
|
||||||
default: throw new Error('Unrecognized literal style expression: ' + JSON.stringify(value));
|
|
||||||
}
|
|
||||||
} else if (typeof value === 'number') {
|
|
||||||
return formatNumber(value);
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid value type in expression: ' + JSON.stringify(value));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} StyleParseResult
|
* @typedef {Object} StyleParseResult
|
||||||
* @property {ShaderParameters} params Symbol shader params.
|
* @property {ShaderBuilder} builder Shader builder pre-configured according to a given style
|
||||||
* @property {Object.<string,import("./Helper").UniformValue>} uniforms Uniform definitions.
|
* @property {Object.<string,import("./Helper").UniformValue>} uniforms Uniform definitions.
|
||||||
* @property {Array<import("../renderer/webgl/PointsLayer").CustomAttribute>} attributes Attribute descriptions.
|
* @property {Array<import("../renderer/webgl/PointsLayer").CustomAttribute>} attributes Attribute descriptions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a {@link import("../style/LiteralStyle").LiteralSymbolStyle} object and outputs shader parameters to be
|
* Parses a {@link import("../style/LiteralStyle").LiteralStyle} object and returns a {@link ShaderBuilder}
|
||||||
* then fed to {@link getSymbolVertexShader} and {@link getSymbolFragmentShader}.
|
* object that has been configured according to the given style, as well as `attributes` and `uniforms`
|
||||||
|
* arrays to be fed to the `WebGLPointsRenderer` class.
|
||||||
*
|
*
|
||||||
* Also returns `uniforms` and `attributes` properties as expected by the
|
* Also returns `uniforms` and `attributes` properties as expected by the
|
||||||
* {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}.
|
* {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}.
|
||||||
*
|
*
|
||||||
* @param {import("../style/LiteralStyle").LiteralSymbolStyle} style Symbol style.
|
* @param {import("../style/LiteralStyle").LiteralStyle} style Literal style.
|
||||||
* @returns {StyleParseResult} Result containing shader params, attributes and uniforms.
|
* @returns {StyleParseResult} Result containing shader params, attributes and uniforms.
|
||||||
*/
|
*/
|
||||||
export function parseSymbolStyle(style) {
|
export function parseLiteralStyle(style) {
|
||||||
const size = Array.isArray(style.size) && typeof style.size[0] == 'number' ?
|
const symbStyle = style.symbol;
|
||||||
style.size : [style.size, style.size];
|
const size = symbStyle.size !== undefined ? symbStyle.size : 1;
|
||||||
const color = (typeof style.color === 'string' ?
|
const color = symbStyle.color || 'white';
|
||||||
asArray(style.color).map(function(c, i) {
|
const texCoord = symbStyle.textureCoord || [0, 0, 1, 1];
|
||||||
return i < 3 ? c / 255 : c;
|
const offset = symbStyle.offset || [0, 0];
|
||||||
}) :
|
const opacity = symbStyle.opacity !== undefined ? symbStyle.opacity : 1;
|
||||||
style.color || [255, 255, 255, 1]);
|
|
||||||
const texCoord = style.textureCoord || [0, 0, 1, 1];
|
|
||||||
const offset = style.offset || [0, 0];
|
|
||||||
const opacity = style.opacity !== undefined ? style.opacity : 1;
|
|
||||||
|
|
||||||
let attributes = [];
|
/**
|
||||||
const varyings = [];
|
* @type {import("../style/expressions.js").ParsingContext}
|
||||||
function pA(value) {
|
*/
|
||||||
return parse(value, attributes, 'a_');
|
const vertContext = {
|
||||||
}
|
inFragmentShader: false,
|
||||||
function pV(value) {
|
variables: [],
|
||||||
return parse(value, varyings, 'v_');
|
attributes: [],
|
||||||
}
|
stringLiteralsMap: {}
|
||||||
|
};
|
||||||
|
const parsedSize = expressionToGlsl(vertContext, size, ValueTypes.NUMBER_ARRAY | ValueTypes.NUMBER);
|
||||||
|
const parsedOffset = expressionToGlsl(vertContext, offset, ValueTypes.NUMBER_ARRAY);
|
||||||
|
const parsedTexCoord = expressionToGlsl(vertContext, texCoord, ValueTypes.NUMBER_ARRAY);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import("../style/expressions.js").ParsingContext}
|
||||||
|
*/
|
||||||
|
const fragContext = {
|
||||||
|
inFragmentShader: true,
|
||||||
|
variables: vertContext.variables,
|
||||||
|
attributes: [],
|
||||||
|
stringLiteralsMap: vertContext.stringLiteralsMap
|
||||||
|
};
|
||||||
|
const parsedColor = expressionToGlsl(fragContext, color, ValueTypes.COLOR);
|
||||||
|
const parsedOpacity = expressionToGlsl(fragContext, opacity, ValueTypes.NUMBER);
|
||||||
|
|
||||||
let opacityFilter = '1.0';
|
let opacityFilter = '1.0';
|
||||||
const visibleSize = pV(size[0]);
|
const visibleSize = `vec2(${expressionToGlsl(fragContext, size, ValueTypes.NUMBER_ARRAY | ValueTypes.NUMBER)}).x`;
|
||||||
switch (style.symbolType) {
|
switch (symbStyle.symbolType) {
|
||||||
case 'square': break;
|
case 'square': break;
|
||||||
case 'image': break;
|
case 'image': break;
|
||||||
// taken from https://thebookofshaders.com/07/
|
// taken from https://thebookofshaders.com/07/
|
||||||
@@ -261,53 +422,74 @@ export function parseSymbolStyle(style) {
|
|||||||
opacityFilter = `(1.0-smoothstep(.5-3./${visibleSize},.5,cos(floor(.5+${a}/2.094395102)*2.094395102-${a})*length(${st})))`;
|
opacityFilter = `(1.0-smoothstep(.5-3./${visibleSize},.5,cos(floor(.5+${a}/2.094395102)*2.094395102-${a})*length(${st})))`;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: throw new Error('Unexpected symbol type: ' + style.symbolType);
|
default: throw new Error('Unexpected symbol type: ' + symbStyle.symbolType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {import('../webgl/ShaderBuilder.js').ShaderParameters} */
|
const builder = new ShaderBuilder()
|
||||||
const params = {
|
.setSizeExpression(`vec2(${parsedSize})`)
|
||||||
uniforms: [],
|
.setSymbolOffsetExpression(parsedOffset)
|
||||||
colorExpression: `vec4(${pV(color[0])}, ${pV(color[1])}, ${pV(color[2])}, ${pV(color[3])})` +
|
.setTextureCoordinateExpression(parsedTexCoord)
|
||||||
` * vec4(1.0, 1.0, 1.0, ${pV(opacity)} * ${opacityFilter})`,
|
.setSymbolRotateWithView(!!symbStyle.rotateWithView)
|
||||||
sizeExpression: `vec2(${pA(size[0])}, ${pA(size[1])})`,
|
.setColorExpression(
|
||||||
offsetExpression: `vec2(${pA(offset[0])}, ${pA(offset[1])})`,
|
`vec4(${parsedColor}.rgb, ${parsedColor}.a * ${parsedOpacity} * ${opacityFilter})`);
|
||||||
texCoordExpression: `vec4(${pA(texCoord[0])}, ${pA(texCoord[1])}, ${pA(texCoord[2])}, ${pA(texCoord[3])})`,
|
|
||||||
rotateWithView: !!style.rotateWithView
|
|
||||||
};
|
|
||||||
|
|
||||||
attributes = attributes.concat(varyings).filter(function(attrName, index, arr) {
|
if (style.filter) {
|
||||||
return arr.indexOf(attrName) === index;
|
const parsedFilter = expressionToGlsl(fragContext, style.filter, ValueTypes.BOOLEAN);
|
||||||
});
|
builder.setFragmentDiscardExpression(`!${parsedFilter}`);
|
||||||
params.attributes = attributes.map(function(attributeName) {
|
}
|
||||||
return `float a_${attributeName}`;
|
|
||||||
});
|
|
||||||
params.varyings = varyings.map(function(attributeName) {
|
|
||||||
return {
|
|
||||||
name: `v_${attributeName}`,
|
|
||||||
type: 'float',
|
|
||||||
expression: `a_${attributeName}`
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @type {Object.<string,import("../webgl/Helper").UniformValue>} */
|
/** @type {Object.<string,import("../webgl/Helper").UniformValue>} */
|
||||||
const uniforms = {};
|
const uniforms = {};
|
||||||
|
|
||||||
if (style.symbolType === 'image' && style.src) {
|
// define one uniform per variable
|
||||||
|
fragContext.variables.forEach(function(varName) {
|
||||||
|
builder.addUniform(`float u_${varName}`);
|
||||||
|
uniforms[`u_${varName}`] = function() {
|
||||||
|
if (!style.variables || style.variables[varName] === undefined) {
|
||||||
|
throw new Error(`The following variable is missing from the style: ${varName}`);
|
||||||
|
}
|
||||||
|
let value = style.variables[varName];
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
value = parseFloat(stringToGlsl(vertContext, value));
|
||||||
|
}
|
||||||
|
return value !== undefined ? value : -9999999; // to avoid matching with the first string literal
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (symbStyle.symbolType === 'image' && symbStyle.src) {
|
||||||
const texture = new Image();
|
const texture = new Image();
|
||||||
texture.src = style.src;
|
texture.src = symbStyle.src;
|
||||||
params.uniforms.push('sampler2D u_texture');
|
builder.addUniform('sampler2D u_texture')
|
||||||
params.colorExpression = params.colorExpression +
|
.setColorExpression(builder.getColorExpression() +
|
||||||
' * texture2D(u_texture, v_texCoord)';
|
' * texture2D(u_texture, v_texCoord)');
|
||||||
uniforms['u_texture'] = texture;
|
uniforms['u_texture'] = texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for each feature attribute used in the fragment shader, define a varying that will be used to pass data
|
||||||
|
// from the vertex to the fragment shader, as well as an attribute in the vertex shader (if not already present)
|
||||||
|
fragContext.attributes.forEach(function(attrName) {
|
||||||
|
if (vertContext.attributes.indexOf(attrName) === -1) {
|
||||||
|
vertContext.attributes.push(attrName);
|
||||||
|
}
|
||||||
|
builder.addVarying(`v_${attrName}`, 'float', `a_${attrName}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// for each feature attribute used in the vertex shader, define an attribute in the vertex shader.
|
||||||
|
vertContext.attributes.forEach(function(attrName) {
|
||||||
|
builder.addAttribute(`float a_${attrName}`);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
params: params,
|
builder: builder,
|
||||||
attributes: attributes.map(function(attributeName) {
|
attributes: vertContext.attributes.map(function(attributeName) {
|
||||||
return {
|
return {
|
||||||
name: attributeName,
|
name: attributeName,
|
||||||
callback: function(feature) {
|
callback: function(feature) {
|
||||||
return feature.get(attributeName) || 0;
|
let value = feature.get(attributeName);
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
value = parseFloat(stringToGlsl(vertContext, value));
|
||||||
|
}
|
||||||
|
return value !== undefined ? value : -9999999; // to avoid matching with the first string literal
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
asArray,
|
asArray,
|
||||||
asString,
|
asString,
|
||||||
fromString,
|
fromString,
|
||||||
|
isStringColor,
|
||||||
normalize,
|
normalize,
|
||||||
toString
|
toString
|
||||||
} from '../../../src/ol/color.js';
|
} from '../../../src/ol/color.js';
|
||||||
@@ -159,4 +160,18 @@ describe('ol.color', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isValid()', function() {
|
||||||
|
|
||||||
|
it('correctly detects valid colors', function() {
|
||||||
|
expect(isStringColor('rgba(1,3,4,0.4)')).to.be(true);
|
||||||
|
expect(isStringColor('rgb(1,3,4)')).to.be(true);
|
||||||
|
expect(isStringColor('lightgreen')).to.be(true);
|
||||||
|
expect(isStringColor('yellow')).to.be(true);
|
||||||
|
expect(isStringColor('GREEN')).to.be(true);
|
||||||
|
expect(isStringColor('notacolor')).to.be(false);
|
||||||
|
expect(isStringColor('red_')).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,3 +54,27 @@ describe('ol.control.Control\'s target', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ol.control.Control\'s event target', function() {
|
||||||
|
it('is the Control when the Control uses the default target', function(done) {
|
||||||
|
const ctrl = new Control({element: document.createElement('div')});
|
||||||
|
ctrl.on('test-event', function(e) {
|
||||||
|
expect(e.target).to.be(ctrl);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
ctrl.dispatchEvent('test-event');
|
||||||
|
ctrl.dispose();
|
||||||
|
});
|
||||||
|
it('is the Control when the Control has a custom target', function(done) {
|
||||||
|
const ctrl = new Control({
|
||||||
|
element: document.createElement('div'),
|
||||||
|
target: document.createElement('div')
|
||||||
|
});
|
||||||
|
ctrl.on('test-event', function(e) {
|
||||||
|
expect(e.target).to.be(ctrl);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
ctrl.dispatchEvent('test-event');
|
||||||
|
ctrl.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ describe('ol.layer.Graticule', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('creates a graticule with labels', function() {
|
it('creates a graticule with labels', function() {
|
||||||
|
const feature = new Feature();
|
||||||
graticule = new Graticule({
|
graticule = new Graticule({
|
||||||
showLabels: true
|
showLabels: true
|
||||||
});
|
});
|
||||||
@@ -51,6 +52,10 @@ describe('ol.layer.Graticule', function() {
|
|||||||
expect(graticule.parallelsLabels_.length).to.be(3);
|
expect(graticule.parallelsLabels_.length).to.be(3);
|
||||||
expect(graticule.parallelsLabels_[0].text).to.be('0° 00′ 00″');
|
expect(graticule.parallelsLabels_[0].text).to.be('0° 00′ 00″');
|
||||||
expect(graticule.parallelsLabels_[0].geom.getCoordinates()[1]).to.roughlyEqual(0, 1e-9);
|
expect(graticule.parallelsLabels_[0].geom.getCoordinates()[1]).to.roughlyEqual(0, 1e-9);
|
||||||
|
feature.set('graticule_label', graticule.meridiansLabels_[0].text);
|
||||||
|
expect(graticule.lonLabelStyle_(feature).getText().getText()).to.be('0° 00′ 00″');
|
||||||
|
feature.set('graticule_label', graticule.parallelsLabels_[0].text);
|
||||||
|
expect(graticule.latLabelStyle_(feature).getText().getText()).to.be('0° 00′ 00″');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has a default stroke style', function() {
|
it('has a default stroke style', function() {
|
||||||
@@ -105,6 +110,10 @@ describe('ol.layer.Graticule', function() {
|
|||||||
expect(graticule.parallelsLabels_[0].text).to.be('lat: 0');
|
expect(graticule.parallelsLabels_[0].text).to.be('lat: 0');
|
||||||
expect(graticule.lonLabelStyle_(feature).getText()).to.eql(lonLabelStyle);
|
expect(graticule.lonLabelStyle_(feature).getText()).to.eql(lonLabelStyle);
|
||||||
expect(graticule.latLabelStyle_(feature).getText()).to.eql(latLabelStyle);
|
expect(graticule.latLabelStyle_(feature).getText()).to.eql(latLabelStyle);
|
||||||
|
feature.set('graticule_label', graticule.meridiansLabels_[0].text);
|
||||||
|
expect(graticule.lonLabelStyle_(feature).getText().getText()).to.be('lon: 0');
|
||||||
|
feature.set('graticule_label', graticule.parallelsLabels_[0].text);
|
||||||
|
expect(graticule.latLabelStyle_(feature).getText().getText()).to.be('lat: 0');
|
||||||
expect(graticule.lonLabelPosition_).to.be(0.9);
|
expect(graticule.lonLabelPosition_).to.be(0.9);
|
||||||
expect(graticule.latLabelPosition_).to.be(0.1);
|
expect(graticule.latLabelPosition_).to.be(0.1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import HeatmapLayer from '../../../../src/ol/layer/Heatmap.js';
|
import HeatmapLayer from '../../../../src/ol/layer/Heatmap.js';
|
||||||
|
import Feature from '../../../../src/ol/Feature.js';
|
||||||
|
import Point from '../../../../src/ol/geom/Point.js';
|
||||||
|
import VectorSource from '../../../../src/ol/source/Vector.js';
|
||||||
|
import Map from '../../../../src/ol/Map.js';
|
||||||
|
import View from '../../../../src/ol/View.js';
|
||||||
|
|
||||||
describe('ol.layer.Heatmap', function() {
|
describe('ol.layer.Heatmap', function() {
|
||||||
|
|
||||||
@@ -12,4 +16,66 @@ describe('ol.layer.Heatmap', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('hit detection', function() {
|
||||||
|
|
||||||
|
it('hit detects two distinct features', function(done) {
|
||||||
|
const target = document.createElement('div');
|
||||||
|
target.style.width = '300px';
|
||||||
|
target.style.height = '300px';
|
||||||
|
document.body.appendChild(target);
|
||||||
|
|
||||||
|
const feature = new Feature({geometry: new Point([0, 0]), id: 1, weight: 10});
|
||||||
|
const feature2 = new Feature({geometry: new Point([14, 14]), id: 2, weight: 10});
|
||||||
|
|
||||||
|
const source = new VectorSource({
|
||||||
|
features: [feature, feature2]
|
||||||
|
});
|
||||||
|
const layer = new HeatmapLayer({
|
||||||
|
source: source,
|
||||||
|
blur: 10,
|
||||||
|
radius: 10
|
||||||
|
});
|
||||||
|
const map = new Map({
|
||||||
|
layers: [layer],
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
resolution: 0.1
|
||||||
|
}),
|
||||||
|
target: target
|
||||||
|
});
|
||||||
|
map.render();
|
||||||
|
|
||||||
|
function hitTest(coordinate) {
|
||||||
|
const features = map.getFeaturesAtPixel(
|
||||||
|
map.getPixelFromCoordinate(coordinate)
|
||||||
|
);
|
||||||
|
return features.length ? features[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderer = layer.getRenderer();
|
||||||
|
renderer.worker_.addEventListener('message', function(event) {
|
||||||
|
if (!renderer.hitRenderInstructions_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
map.renderSync();
|
||||||
|
|
||||||
|
let res;
|
||||||
|
|
||||||
|
res = hitTest([0, 0]);
|
||||||
|
expect(res).to.be(feature);
|
||||||
|
res = hitTest([20, 0]);
|
||||||
|
expect(res).to.be(null);
|
||||||
|
res = hitTest([14, 14]);
|
||||||
|
expect(res).to.be(feature2);
|
||||||
|
res = hitTest([0, 14]);
|
||||||
|
expect(res).to.be(null);
|
||||||
|
|
||||||
|
document.body.removeChild(target);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import Layer from '../../../../src/ol/layer/Layer.js';
|
|||||||
import VectorLayer from '../../../../src/ol/layer/Vector.js';
|
import VectorLayer from '../../../../src/ol/layer/Vector.js';
|
||||||
import VectorSource from '../../../../src/ol/source/Vector.js';
|
import VectorSource from '../../../../src/ol/source/Vector.js';
|
||||||
import Style, {createDefaultStyle} from '../../../../src/ol/style/Style.js';
|
import Style, {createDefaultStyle} from '../../../../src/ol/style/Style.js';
|
||||||
|
import Feature from '../../../../src/ol/Feature.js';
|
||||||
|
import Point from '../../../../src/ol/geom/Point.js';
|
||||||
|
import Map from '../../../../src/ol/Map.js';
|
||||||
|
import View from '../../../../src/ol/View.js';
|
||||||
|
|
||||||
|
|
||||||
describe('ol.layer.Vector', function() {
|
describe('ol.layer.Vector', function() {
|
||||||
@@ -123,4 +127,55 @@ describe('ol.layer.Vector', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getFeatures()', function() {
|
||||||
|
|
||||||
|
let map, layer;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
layer = new VectorLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
features: [
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([-1000000, 0]),
|
||||||
|
name: 'feature1'
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([1000000, 0]),
|
||||||
|
name: 'feture2'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.style.width = '256px';
|
||||||
|
container.style.height = '256px';
|
||||||
|
document.body.appendChild(container);
|
||||||
|
map = new Map({
|
||||||
|
target: container,
|
||||||
|
layers: [
|
||||||
|
layer
|
||||||
|
],
|
||||||
|
view: new View({
|
||||||
|
zoom: 2,
|
||||||
|
center: [0, 0]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
document.body.removeChild(map.getTargetElement());
|
||||||
|
map.setTarget(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects features properly', function(done) {
|
||||||
|
map.renderSync();
|
||||||
|
const pixel = map.getPixelFromCoordinate([-1000000, 0]);
|
||||||
|
layer.getFeatures(pixel).then(function(features) {
|
||||||
|
expect(features[0].get('name')).to.be('feature1');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
61
test/spec/ol/layer/vectorimage.test.js
Normal file
61
test/spec/ol/layer/vectorimage.test.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import Feature from '../../../../src/ol/Feature.js';
|
||||||
|
import Point from '../../../../src/ol/geom/Point.js';
|
||||||
|
import Map from '../../../../src/ol/Map.js';
|
||||||
|
import View from '../../../../src/ol/View.js';
|
||||||
|
import VectorImageLayer from '../../../../src/ol/layer/VectorImage.js';
|
||||||
|
import VectorSource from '../../../../src/ol/source/Vector.js';
|
||||||
|
|
||||||
|
describe('ol/layer/VectorImage', function() {
|
||||||
|
|
||||||
|
describe('#getFeatures()', function() {
|
||||||
|
|
||||||
|
let map, layer;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
layer = new VectorImageLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
features: [
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([-1000000, 0]),
|
||||||
|
name: 'feature1'
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([1000000, 0]),
|
||||||
|
name: 'feture2'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.style.width = '256px';
|
||||||
|
container.style.height = '256px';
|
||||||
|
document.body.appendChild(container);
|
||||||
|
map = new Map({
|
||||||
|
target: container,
|
||||||
|
layers: [
|
||||||
|
layer
|
||||||
|
],
|
||||||
|
view: new View({
|
||||||
|
zoom: 2,
|
||||||
|
center: [0, 0]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
document.body.removeChild(map.getTargetElement());
|
||||||
|
map.setTarget(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects features properly', function(done) {
|
||||||
|
map.renderSync();
|
||||||
|
const pixel = map.getPixelFromCoordinate([-1000000, 0]);
|
||||||
|
layer.getFeatures(pixel).then(function(features) {
|
||||||
|
expect(features[0].get('name')).to.be('feature1');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
import VectorTileLayer from '../../../../src/ol/layer/VectorTile.js';
|
import VectorTileLayer from '../../../../src/ol/layer/VectorTile.js';
|
||||||
import VectorTileSource from '../../../../src/ol/source/VectorTile.js';
|
import VectorTileSource from '../../../../src/ol/source/VectorTile.js';
|
||||||
|
import GeoJSON from '../../../../src/ol/format/GeoJSON.js';
|
||||||
|
import View from '../../../../src/ol/View.js';
|
||||||
|
import Map from '../../../../src/ol/Map.js';
|
||||||
|
import {fromLonLat} from '../../../../src/ol/proj.js';
|
||||||
|
|
||||||
|
|
||||||
describe('ol.layer.VectorTile', function() {
|
describe('ol.layer.VectorTile', function() {
|
||||||
@@ -57,4 +61,74 @@ describe('ol.layer.VectorTile', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getFeatures()', function() {
|
||||||
|
|
||||||
|
let map, layer;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
layer = new VectorTileLayer({
|
||||||
|
source: new VectorTileSource({
|
||||||
|
format: new GeoJSON(),
|
||||||
|
url: `data:application/json;charset=utf-8,
|
||||||
|
{
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [-36, 0]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "feature1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [36, 0]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "feature2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.style.width = '256px';
|
||||||
|
container.style.height = '256px';
|
||||||
|
document.body.appendChild(container);
|
||||||
|
map = new Map({
|
||||||
|
target: container,
|
||||||
|
layers: [
|
||||||
|
layer
|
||||||
|
],
|
||||||
|
view: new View({
|
||||||
|
zoom: 0,
|
||||||
|
center: [0, 0]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
document.body.removeChild(map.getTargetElement());
|
||||||
|
map.setTarget(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects features properly', function(done) {
|
||||||
|
map.once('rendercomplete', function() {
|
||||||
|
const pixel = map.getPixelFromCoordinate(fromLonLat([-36, 0]));
|
||||||
|
layer.getFeatures(pixel).then(function(features) {
|
||||||
|
expect(features[0].get('name')).to.be('feature1');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -662,6 +662,21 @@ describe('ol.Map', function() {
|
|||||||
expect(map.handleResize_).to.be.ok();
|
expect(map.handleResize_).to.be.ok();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles touch-action on focus and blur', function() {
|
||||||
|
expect(map.focusHandlerKeys_).to.be(null);
|
||||||
|
expect(map.getViewport().getAttribute('touch-action')).to.be('none');
|
||||||
|
const target = document.createElement('div');
|
||||||
|
target.setAttribute('tabindex', 1);
|
||||||
|
map.setTarget(target);
|
||||||
|
expect(Array.isArray(map.focusHandlerKeys_)).to.be(true);
|
||||||
|
expect(map.getViewport().getAttribute('touch-action')).to.be('auto');
|
||||||
|
target.dispatchEvent(new Event('focus'));
|
||||||
|
expect(map.getViewport().getAttribute('touch-action')).to.be('none');
|
||||||
|
map.setTarget(null);
|
||||||
|
expect(map.focusHandlerKeys_).to.be(null);
|
||||||
|
expect(map.getViewport().getAttribute('touch-action')).to.be('none');
|
||||||
|
});
|
||||||
|
|
||||||
describe('call setTarget with null', function() {
|
describe('call setTarget with null', function() {
|
||||||
it('unregisters the viewport resize listener', function() {
|
it('unregisters the viewport resize listener', function() {
|
||||||
map.setTarget(null);
|
map.setTarget(null);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import VectorSource from '../../../../../src/ol/source/Vector.js';
|
|||||||
import CanvasVectorImageLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorImageLayer.js';
|
import CanvasVectorImageLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorImageLayer.js';
|
||||||
import {get as getProjection} from '../../../../../src/ol/proj.js';
|
import {get as getProjection} from '../../../../../src/ol/proj.js';
|
||||||
import {scaleFromCenter} from '../../../../../src/ol/extent.js';
|
import {scaleFromCenter} from '../../../../../src/ol/extent.js';
|
||||||
|
import {create} from '../../../../../src/ol/transform.js';
|
||||||
|
|
||||||
|
|
||||||
describe('ol/renderer/canvas/VectorImageLayer', function() {
|
describe('ol/renderer/canvas/VectorImageLayer', function() {
|
||||||
@@ -39,7 +40,9 @@ describe('ol/renderer/canvas/VectorImageLayer', function() {
|
|||||||
layerIndex: 0,
|
layerIndex: 0,
|
||||||
extent: extent,
|
extent: extent,
|
||||||
viewHints: [],
|
viewHints: [],
|
||||||
|
pixelToCoordinateTransform: create(),
|
||||||
viewState: {
|
viewState: {
|
||||||
|
center: [0, 0],
|
||||||
projection: projection,
|
projection: projection,
|
||||||
resolution: 1,
|
resolution: 1,
|
||||||
rotation: 0
|
rotation: 0
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
|
|||||||
const frameState = {
|
const frameState = {
|
||||||
layerStatesArray: [{}],
|
layerStatesArray: [{}],
|
||||||
viewState: {
|
viewState: {
|
||||||
|
center: [0, 0],
|
||||||
resolution: 1,
|
resolution: 1,
|
||||||
rotation: 0
|
rotation: 0
|
||||||
}
|
}
|
||||||
@@ -234,6 +235,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
|
|||||||
frameState = {
|
frameState = {
|
||||||
viewHints: [],
|
viewHints: [],
|
||||||
viewState: {
|
viewState: {
|
||||||
|
center: [0, 0],
|
||||||
projection: projection,
|
projection: projection,
|
||||||
resolution: 1,
|
resolution: 1,
|
||||||
rotation: 0
|
rotation: 0
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import VectorTileRenderType from '../../../../../src/ol/layer/VectorTileRenderTy
|
|||||||
import {getUid} from '../../../../../src/ol/util.js';
|
import {getUid} from '../../../../../src/ol/util.js';
|
||||||
import TileLayer from '../../../../../src/ol/layer/Tile.js';
|
import TileLayer from '../../../../../src/ol/layer/Tile.js';
|
||||||
import XYZ from '../../../../../src/ol/source/XYZ.js';
|
import XYZ from '../../../../../src/ol/source/XYZ.js';
|
||||||
|
import {create} from '../../../../../src/ol/transform.js';
|
||||||
|
|
||||||
|
|
||||||
describe('ol.renderer.canvas.VectorTileLayer', function() {
|
describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||||
@@ -262,6 +263,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
|||||||
layerIndex: 0,
|
layerIndex: 0,
|
||||||
extent: proj.getExtent(),
|
extent: proj.getExtent(),
|
||||||
pixelRatio: 1,
|
pixelRatio: 1,
|
||||||
|
pixelToCoordinateTransform: create(),
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
viewHints: [],
|
viewHints: [],
|
||||||
viewState: {
|
viewState: {
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ describe('ol.renderer.webgl.PointsLayer', function() {
|
|||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
layer = new VectorLayer({
|
layer = new VectorLayer({
|
||||||
source: new VectorSource()
|
source: new VectorSource(),
|
||||||
|
renderBuffer: 10
|
||||||
});
|
});
|
||||||
renderer = new WebGLPointsLayerRenderer(layer, {
|
renderer = new WebGLPointsLayerRenderer(layer, {
|
||||||
vertexShader: simpleVertexShader,
|
vertexShader: simpleVertexShader,
|
||||||
@@ -233,6 +234,35 @@ describe('ol.renderer.webgl.PointsLayer', function() {
|
|||||||
renderer.prepareFrame(frameState);
|
renderer.prepareFrame(frameState);
|
||||||
expect(spy.callCount).to.be(2);
|
expect(spy.callCount).to.be(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('triggers source loading when the extent changes', function() {
|
||||||
|
const spy = sinon.spy(layer.getSource(), 'loadFeatures');
|
||||||
|
|
||||||
|
renderer.prepareFrame(frameState);
|
||||||
|
expect(spy.callCount).to.be(1);
|
||||||
|
|
||||||
|
renderer.prepareFrame(frameState);
|
||||||
|
expect(spy.callCount).to.be(1);
|
||||||
|
|
||||||
|
frameState.extent = [10, 20, 30, 40];
|
||||||
|
renderer.prepareFrame(frameState);
|
||||||
|
expect(spy.callCount).to.be(2);
|
||||||
|
expect(spy.getCall(1).args[0]).to.eql([0, 10, 40, 50]); // renderBuffer is 10
|
||||||
|
});
|
||||||
|
|
||||||
|
it('triggers source loading when the source revision changes', function() {
|
||||||
|
const spy = sinon.spy(layer.getSource(), 'loadFeatures');
|
||||||
|
|
||||||
|
renderer.prepareFrame(frameState);
|
||||||
|
expect(spy.callCount).to.be(1);
|
||||||
|
|
||||||
|
renderer.prepareFrame(frameState);
|
||||||
|
expect(spy.callCount).to.be(1);
|
||||||
|
|
||||||
|
layer.getSource().changed();
|
||||||
|
renderer.prepareFrame(frameState);
|
||||||
|
expect(spy.callCount).to.be(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#forEachFeatureAtCoordinate', function() {
|
describe('#forEachFeatureAtCoordinate', function() {
|
||||||
|
|||||||
651
test/spec/ol/style/expressions.test.js
Normal file
651
test/spec/ol/style/expressions.test.js
Normal file
@@ -0,0 +1,651 @@
|
|||||||
|
import {
|
||||||
|
arrayToGlsl, colorToGlsl,
|
||||||
|
expressionToGlsl,
|
||||||
|
getValueType, isTypeUnique,
|
||||||
|
numberToGlsl, stringToGlsl,
|
||||||
|
ValueTypes
|
||||||
|
} from '../../../../src/ol/style/expressions.js';
|
||||||
|
|
||||||
|
|
||||||
|
describe('ol.style.expressions', function() {
|
||||||
|
|
||||||
|
describe('numberToGlsl', function() {
|
||||||
|
it('does a simple transform when a fraction is present', function() {
|
||||||
|
expect(numberToGlsl(1.3456)).to.eql('1.3456');
|
||||||
|
});
|
||||||
|
it('adds a fraction separator when missing', function() {
|
||||||
|
expect(numberToGlsl(1)).to.eql('1.0');
|
||||||
|
expect(numberToGlsl(2.0)).to.eql('2.0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('arrayToGlsl', function() {
|
||||||
|
it('outputs numbers with dot separators', function() {
|
||||||
|
expect(arrayToGlsl([1, 0, 3.45, 0.8888])).to.eql('vec4(1.0, 0.0, 3.45, 0.8888)');
|
||||||
|
expect(arrayToGlsl([3, 4])).to.eql('vec2(3.0, 4.0)');
|
||||||
|
});
|
||||||
|
it('throws on invalid lengths', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
arrayToGlsl([3]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
arrayToGlsl([3, 2, 1, 0, -1]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('colorToGlsl', function() {
|
||||||
|
it('normalizes color and outputs numbers with dot separators', function() {
|
||||||
|
expect(colorToGlsl([100, 0, 255])).to.eql('vec4(0.39215686274509803, 0.0, 1.0, 1.0)');
|
||||||
|
expect(colorToGlsl([100, 0, 255, 1])).to.eql('vec4(0.39215686274509803, 0.0, 1.0, 1.0)');
|
||||||
|
});
|
||||||
|
it('handles colors in string format', function() {
|
||||||
|
expect(colorToGlsl('red')).to.eql('vec4(1.0, 0.0, 0.0, 1.0)');
|
||||||
|
expect(colorToGlsl('#00ff99')).to.eql('vec4(0.0, 1.0, 0.6, 1.0)');
|
||||||
|
expect(colorToGlsl('rgb(100, 0, 255)')).to.eql('vec4(0.39215686274509803, 0.0, 1.0, 1.0)');
|
||||||
|
expect(colorToGlsl('rgba(100, 0, 255, 0.3)')).to.eql('vec4(0.39215686274509803, 0.0, 1.0, 0.3)');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('stringToGlsl', function() {
|
||||||
|
let context;
|
||||||
|
beforeEach(function() {
|
||||||
|
context = {
|
||||||
|
stringLiteralsMap: {}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps input string to stable numbers', function() {
|
||||||
|
expect(stringToGlsl(context, 'abcd')).to.eql('0.0');
|
||||||
|
expect(stringToGlsl(context, 'defg')).to.eql('1.0');
|
||||||
|
expect(stringToGlsl(context, 'hijk')).to.eql('2.0');
|
||||||
|
expect(stringToGlsl(context, 'abcd')).to.eql('0.0');
|
||||||
|
expect(stringToGlsl(context, 'def')).to.eql('3.0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isTypeUnique', function() {
|
||||||
|
it('return true if only one value type', function() {
|
||||||
|
expect(isTypeUnique(ValueTypes.NUMBER)).to.eql(true);
|
||||||
|
expect(isTypeUnique(ValueTypes.STRING)).to.eql(true);
|
||||||
|
expect(isTypeUnique(ValueTypes.COLOR)).to.eql(true);
|
||||||
|
});
|
||||||
|
it('return false if several value types', function() {
|
||||||
|
expect(isTypeUnique(ValueTypes.NUMBER | ValueTypes.COLOR)).to.eql(false);
|
||||||
|
expect(isTypeUnique(ValueTypes.ANY)).to.eql(false);
|
||||||
|
});
|
||||||
|
it('return false if no value type', function() {
|
||||||
|
expect(isTypeUnique(ValueTypes.NUMBER & ValueTypes.COLOR)).to.eql(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getValueType', function() {
|
||||||
|
|
||||||
|
it('correctly analyzes a literal value', function() {
|
||||||
|
expect(getValueType(1234)).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType([1, 2, 3, 4])).to.eql(ValueTypes.COLOR | ValueTypes.NUMBER_ARRAY);
|
||||||
|
expect(getValueType([1, 2, 3])).to.eql(ValueTypes.COLOR | ValueTypes.NUMBER_ARRAY);
|
||||||
|
expect(getValueType([1, 2])).to.eql(ValueTypes.NUMBER_ARRAY);
|
||||||
|
expect(getValueType([1, 2, 3, 4, 5])).to.eql(ValueTypes.NUMBER_ARRAY);
|
||||||
|
expect(getValueType('yellow')).to.eql(ValueTypes.COLOR | ValueTypes.STRING);
|
||||||
|
expect(getValueType('#113366')).to.eql(ValueTypes.COLOR | ValueTypes.STRING);
|
||||||
|
expect(getValueType('rgba(252,171,48,0.62)')).to.eql(ValueTypes.COLOR | ValueTypes.STRING);
|
||||||
|
expect(getValueType('abcd')).to.eql(ValueTypes.STRING);
|
||||||
|
expect(getValueType(true)).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on an unsupported type (object)', function(done) {
|
||||||
|
try {
|
||||||
|
getValueType(new Object());
|
||||||
|
} catch (e) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
done(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on an unsupported type (mixed array)', function(done) {
|
||||||
|
try {
|
||||||
|
getValueType([1, true, 'aa']);
|
||||||
|
} catch (e) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
done(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly analyzes operator return types', function() {
|
||||||
|
expect(getValueType(['get', 'myAttr'])).to.eql(ValueTypes.ANY);
|
||||||
|
expect(getValueType(['var', 'myValue'])).to.eql(ValueTypes.ANY);
|
||||||
|
expect(getValueType(['time'])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['zoom'])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['resolution'])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['+', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['-', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['/', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['*', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['clamp', ['get', 'attr2'], ['get', 'attr3'], 20])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['^', 10, 2])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['%', ['time'], 10])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['>', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['>=', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['<', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['<=', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['==', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['!=', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['between', ['get', 'attr4'], -4.0, 5.0])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['!', ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['array', ['get', 'attr4'], 1, 2, 3])).to.eql(ValueTypes.NUMBER_ARRAY);
|
||||||
|
expect(getValueType(['color', ['get', 'attr4'], 1, 2])).to.eql(ValueTypes.COLOR);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('expressionToGlsl', function() {
|
||||||
|
let context;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
context = {
|
||||||
|
variables: [],
|
||||||
|
attributes: [],
|
||||||
|
stringLiteralsMap: {}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly converts expressions to GLSL', function() {
|
||||||
|
expect(expressionToGlsl(context, ['get', 'myAttr'])).to.eql('a_myAttr');
|
||||||
|
expect(expressionToGlsl(context, ['var', 'myValue'])).to.eql('u_myValue');
|
||||||
|
expect(expressionToGlsl(context, ['time'])).to.eql('u_time');
|
||||||
|
expect(expressionToGlsl(context, ['zoom'])).to.eql('u_zoom');
|
||||||
|
expect(expressionToGlsl(context, ['resolution'])).to.eql('u_resolution');
|
||||||
|
expect(expressionToGlsl(context, ['+', ['*', ['get', 'size'], 0.001], 12])).to.eql('((a_size * 0.001) + 12.0)');
|
||||||
|
expect(expressionToGlsl(context, ['/', ['-', ['get', 'size'], 20], 100])).to.eql('((a_size - 20.0) / 100.0)');
|
||||||
|
expect(expressionToGlsl(context, ['clamp', ['get', 'attr2'], ['get', 'attr3'], 20])).to.eql('clamp(a_attr2, a_attr3, 20.0)');
|
||||||
|
expect(expressionToGlsl(context, ['^', ['%', ['time'], 10], 2])).to.eql('pow(mod(u_time, 10.0), 2.0)');
|
||||||
|
expect(expressionToGlsl(context, ['>', 10, ['get', 'attr4']])).to.eql('(10.0 > a_attr4)');
|
||||||
|
expect(expressionToGlsl(context, ['>=', 10, ['get', 'attr4']])).to.eql('(10.0 >= a_attr4)');
|
||||||
|
expect(expressionToGlsl(context, ['<', 10, ['get', 'attr4']])).to.eql('(10.0 < a_attr4)');
|
||||||
|
expect(expressionToGlsl(context, ['<=', 10, ['get', 'attr4']])).to.eql('(10.0 <= a_attr4)');
|
||||||
|
expect(expressionToGlsl(context, ['==', 10, ['get', 'attr4']])).to.eql('(10.0 == a_attr4)');
|
||||||
|
expect(expressionToGlsl(context, ['!=', 10, ['get', 'attr4']])).to.eql('(10.0 != a_attr4)');
|
||||||
|
expect(expressionToGlsl(context, ['between', ['get', 'attr4'], -4.0, 5.0])).to.eql('(a_attr4 >= -4.0 && a_attr4 <= 5.0)');
|
||||||
|
expect(expressionToGlsl(context, ['!', ['get', 'attr4']])).to.eql('(!a_attr4)');
|
||||||
|
expect(expressionToGlsl(context, ['array', ['get', 'attr4'], 1, 2, 3])).to.eql('vec4(a_attr4, 1.0, 2.0, 3.0)');
|
||||||
|
expect(expressionToGlsl(context, ['color', ['get', 'attr4'], 1, 2, 0.5])).to.eql('vec4(a_attr4 / 255.0, 1.0 / 255.0, 2.0 / 255.0, 0.5)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly adapts output for fragment shaders', function() {
|
||||||
|
context.inFragmentShader = true;
|
||||||
|
expect(expressionToGlsl(context, ['get', 'myAttr'])).to.eql('v_myAttr');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly adapts output for fragment shaders', function() {
|
||||||
|
expressionToGlsl(context, ['get', 'myAttr']);
|
||||||
|
expressionToGlsl(context, ['var', 'myVar']);
|
||||||
|
expressionToGlsl(context, ['clamp', ['get', 'attr2'], ['get', 'attr2'], ['get', 'myAttr']]);
|
||||||
|
expressionToGlsl(context, ['*', ['get', 'attr2'], ['var', 'myVar']]);
|
||||||
|
expressionToGlsl(context, ['*', ['get', 'attr3'], ['var', 'myVar2']]);
|
||||||
|
expect(context.attributes).to.eql(['myAttr', 'attr2', 'attr3']);
|
||||||
|
expect(context.variables).to.eql(['myVar', 'myVar2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gives precedence to the string type unless asked otherwise', function() {
|
||||||
|
expect(expressionToGlsl(context, 'lightgreen')).to.eql('0.0');
|
||||||
|
expect(expressionToGlsl(context, 'lightgreen', ValueTypes.COLOR)).to.eql(
|
||||||
|
'vec4(0.5647058823529412, 0.9333333333333333, 0.5647058823529412, 1.0)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on unsupported types for operators', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['var', 1234]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['<', 0, 'aa']);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['+', true, ['get', 'attr']]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['color', 1, 2, 'red']);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['array', 1, '2', 3]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws with the wrong number of arguments', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['var', 1234, 456]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['<', 4]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['+']);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['array', 1]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['color', 1, 2, 3, 4, 5]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on invalid expressions', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, null);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('case operator', function() {
|
||||||
|
let context;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
context = {
|
||||||
|
variables: [],
|
||||||
|
attributes: [],
|
||||||
|
stringLiteralsMap: {}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly guesses the output type', function() {
|
||||||
|
expect(getValueType(['case', true, 0, false, [3, 4, 5], 'green']))
|
||||||
|
.to.eql(ValueTypes.NONE);
|
||||||
|
expect(getValueType(['case', true, 0, false, 1, 2]))
|
||||||
|
.to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['case', true, [0, 0, 0], true, [1, 2, 3], ['get', 'attr'], [4, 5, 6, 7], [8, 9, 0]]))
|
||||||
|
.to.eql(ValueTypes.COLOR | ValueTypes.NUMBER_ARRAY);
|
||||||
|
expect(getValueType(['case', true, 'red', true, 'yellow', ['get', 'attr'], 'green', 'white']))
|
||||||
|
.to.eql(ValueTypes.COLOR | ValueTypes.STRING);
|
||||||
|
expect(getValueType(['case', true, [0, 0], false, [1, 1], [2, 2]]))
|
||||||
|
.to.eql(ValueTypes.NUMBER_ARRAY);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if no single output type could be inferred', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['case', false, 'red', true, 'yellow', 'green'], ValueTypes.COLOR);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['case', true, 'red', true, 'yellow', 'green']);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['case', true, 'red', false, 'yellow', 'green'], ValueTypes.NUMBER);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['case', true, 'red', false, 'yellow', 'not_a_color'], ValueTypes.COLOR);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if invalid argument count', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['case', true, 0, false, 1]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['case', true, 0]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['case', false]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly parses the expression (colors)', function() {
|
||||||
|
expect(expressionToGlsl(context, ['case', ['>', ['get', 'attr'], 3], 'red', ['>', ['get', 'attr'], 1], 'yellow', 'white'], ValueTypes.COLOR))
|
||||||
|
.to.eql('((a_attr > 3.0) ? vec4(1.0, 0.0, 0.0, 1.0) : ((a_attr > 1.0) ? vec4(1.0, 1.0, 0.0, 1.0) : vec4(1.0, 1.0, 1.0, 1.0)))');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('match operator', function() {
|
||||||
|
let context;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
context = {
|
||||||
|
variables: [],
|
||||||
|
attributes: [],
|
||||||
|
stringLiteralsMap: {}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly guesses the output type', function() {
|
||||||
|
expect(getValueType(['match', ['get', 'attr'], 0, 'red', 1, 'yellow', 'green']))
|
||||||
|
.to.eql(ValueTypes.STRING | ValueTypes.COLOR);
|
||||||
|
expect(getValueType(['match', ['get', 'attr'], 0, 'not_a_color', 1, 'yellow', 'green']))
|
||||||
|
.to.eql(ValueTypes.STRING);
|
||||||
|
expect(getValueType(['match', ['get', 'attr'], 0, 'red', 1, 'yellow', 'not_a_color']))
|
||||||
|
.to.eql(ValueTypes.STRING);
|
||||||
|
expect(getValueType(['match', ['get', 'attr'], 0, [1, 1, 0], 1, [1, 0, 1], [0, 1, 1]]))
|
||||||
|
.to.eql(ValueTypes.COLOR | ValueTypes.NUMBER_ARRAY);
|
||||||
|
expect(getValueType(['match', ['get', 'attr'], 0, [1, 1, 0], 1, [1, 0, 1], 'white']))
|
||||||
|
.to.eql(ValueTypes.COLOR);
|
||||||
|
expect(getValueType(['match', ['get', 'attr'], 0, 'red', 1, true, 100]))
|
||||||
|
.to.eql(ValueTypes.NONE);
|
||||||
|
expect(getValueType(['match', ['get', 'attr'], 0, false, 1, true, false]))
|
||||||
|
.to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['match', ['get', 'attr'], 0, 100, 1, 200, 300]))
|
||||||
|
.to.eql(ValueTypes.NUMBER);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if no single output type could be inferred', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['match', ['get', 'attr'], 0, 'red', 1, 'yellow', 'green'], ValueTypes.COLOR);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['match', ['get', 'attr'], 0, 'red', 1, 'yellow', 'green']);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['match', ['get', 'attr'], 0, 'red', 1, 'yellow', 'green'], ValueTypes.NUMBER);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if invalid argument count', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['match', ['get', 'attr'], 0, true, false, false]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['match', ['get', 'attr'], 0, true]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['match', ['get', 'attr'], 0]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly parses the expression (colors)', function() {
|
||||||
|
expect(expressionToGlsl(context, ['match', ['get', 'attr'], 0, 'red', 1, 'yellow', 'white'], ValueTypes.COLOR))
|
||||||
|
.to.eql('(a_attr == 0.0 ? vec4(1.0, 0.0, 0.0, 1.0) : (a_attr == 1.0 ? vec4(1.0, 1.0, 0.0, 1.0) : vec4(1.0, 1.0, 1.0, 1.0)))');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly parses the expression (strings)', function() {
|
||||||
|
function toGlsl(string) {
|
||||||
|
return stringToGlsl(context, string);
|
||||||
|
}
|
||||||
|
expect(expressionToGlsl(context, ['match', ['get', 'attr'], 10, 'red', 20, 'yellow', 'white'], ValueTypes.STRING))
|
||||||
|
.to.eql(`(a_attr == 10.0 ? ${toGlsl('red')} : (a_attr == 20.0 ? ${toGlsl('yellow')} : ${toGlsl('white')}))`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly parses the expression (number arrays)', function() {
|
||||||
|
function toGlsl(string) {
|
||||||
|
return stringToGlsl(context, string);
|
||||||
|
}
|
||||||
|
expect(expressionToGlsl(context, ['match', ['get', 'attr'], 'low', [0, 0], 'high', [0, 1], [1, 0]]))
|
||||||
|
.to.eql(`(a_attr == ${toGlsl('low')} ? vec2(0.0, 0.0) : (a_attr == ${toGlsl('high')} ? vec2(0.0, 1.0) : vec2(1.0, 0.0)))`);
|
||||||
|
expect(expressionToGlsl(context, ['match', ['get', 'attr'], 0, [0, 0, 1, 1], 1, [1, 1, 2, 2], 2, [2, 2, 3, 3], [3, 3, 4, 4]], ValueTypes.NUMBER_ARRAY))
|
||||||
|
.to.eql('(a_attr == 0.0 ? vec4(0.0, 0.0, 1.0, 1.0) : (a_attr == 1.0 ? vec4(1.0, 1.0, 2.0, 2.0) : (a_attr == 2.0 ? vec4(2.0, 2.0, 3.0, 3.0) : vec4(3.0, 3.0, 4.0, 4.0))))');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('interpolate operator', function() {
|
||||||
|
let context;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
context = {
|
||||||
|
variables: [],
|
||||||
|
attributes: [],
|
||||||
|
stringLiteralsMap: {}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly guesses the output type', function() {
|
||||||
|
expect(getValueType(['interpolate', ['linear'], ['get', 'attr'], 0, 'red', 100, 'yellow']))
|
||||||
|
.to.eql(ValueTypes.COLOR);
|
||||||
|
expect(getValueType(['interpolate', ['linear'], ['get', 'attr'], 0, [1, 2, 3], 1, [0, 0, 0, 4]]))
|
||||||
|
.to.eql(ValueTypes.COLOR);
|
||||||
|
expect(getValueType(['interpolate', ['linear'], ['get', 'attr'], 1000, -10, 2000, 10]))
|
||||||
|
.to.eql(ValueTypes.NUMBER);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if no single output type could be inferred', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['interpolate', ['linear'], ['get', 'attr'], 1000, -10, 2000, 10], ValueTypes.COLOR);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['interpolate', ['linear'], ['get', 'attr'], 0, [1, 2, 3], 1, 222]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['interpolate', ['linear'], ['get', 'attr'], 0, [1, 2, 3], 1, [0, 0, 0, 4]], ValueTypes.NUMBER);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if invalid argument count', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['interpolate', ['linear'], ['get', 'attr'], 1000]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['interpolate', ['linear'], ['get', 'attr'], 1000, -10, 2000, 10, 5000]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if an invalid interpolation type is given', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['interpolate', 'linear', ['get', 'attr'], 1000, 0, 2000, 1]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['interpolate', ['exponential'], ['get', 'attr'], 1000, -10, 2000, 1]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
|
||||||
|
thrown = false;
|
||||||
|
try {
|
||||||
|
expressionToGlsl(context, ['interpolate', ['not_a_type'], ['get', 'attr'], 1000, -10, 2000, 1]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly parses the expression (colors, linear)', function() {
|
||||||
|
expect(expressionToGlsl(context,
|
||||||
|
['interpolate', ['linear'], ['get', 'attr'], 1000, [255, 0, 0], 2000, [0, 255, 0]]
|
||||||
|
)).to.eql(
|
||||||
|
'mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.0, 1.0, 0.0, 1.0), pow(clamp((a_attr - 1000.0) / (2000.0 - 1000.0), 0.0, 1.0), 1.0))');
|
||||||
|
expect(expressionToGlsl(context,
|
||||||
|
['interpolate', ['linear'], ['get', 'attr'], 1000, [255, 0, 0], 2000, [0, 255, 0], 5000, [0, 0, 255]]
|
||||||
|
)).to.eql(
|
||||||
|
'mix(mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.0, 1.0, 0.0, 1.0), pow(clamp((a_attr - 1000.0) / (2000.0 - 1000.0), 0.0, 1.0), 1.0)), vec4(0.0, 0.0, 1.0, 1.0), pow(clamp((a_attr - 2000.0) / (5000.0 - 2000.0), 0.0, 1.0), 1.0))');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly parses the expression (number, linear)', function() {
|
||||||
|
expect(expressionToGlsl(context,
|
||||||
|
['interpolate', ['linear'], ['get', 'attr'], 1000, -10, 2000, 0, 5000, 10]
|
||||||
|
)).to.eql(
|
||||||
|
'mix(mix(-10.0, 0.0, pow(clamp((a_attr - 1000.0) / (2000.0 - 1000.0), 0.0, 1.0), 1.0)), 10.0, pow(clamp((a_attr - 2000.0) / (5000.0 - 2000.0), 0.0, 1.0), 1.0))');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly parses the expression (number, exponential)', function() {
|
||||||
|
expect(expressionToGlsl(context,
|
||||||
|
['interpolate', ['exponential', 0.5], ['get', 'attr'], 1000, -10, 2000, 0, 5000, 10]
|
||||||
|
)).to.eql(
|
||||||
|
'mix(mix(-10.0, 0.0, pow(clamp((a_attr - 1000.0) / (2000.0 - 1000.0), 0.0, 1.0), 0.5)), 10.0, pow(clamp((a_attr - 2000.0) / (5000.0 - 2000.0), 0.0, 1.0), 0.5))');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('complex expressions', function() {
|
||||||
|
let context;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
context = {
|
||||||
|
variables: [],
|
||||||
|
attributes: [],
|
||||||
|
stringLiteralsMap: {}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly parses a combination of interpolate, match, color and number', function() {
|
||||||
|
const expression = ['interpolate',
|
||||||
|
['linear'],
|
||||||
|
['^',
|
||||||
|
['/',
|
||||||
|
['%',
|
||||||
|
['+',
|
||||||
|
['time'],
|
||||||
|
[
|
||||||
|
'interpolate',
|
||||||
|
['linear'],
|
||||||
|
['get', 'year'],
|
||||||
|
1850, 0,
|
||||||
|
2015, 8
|
||||||
|
]
|
||||||
|
],
|
||||||
|
8
|
||||||
|
],
|
||||||
|
8
|
||||||
|
],
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
0, 'rgba(255, 255, 0, 0.5)',
|
||||||
|
1, ['match',
|
||||||
|
['get', 'year'],
|
||||||
|
2000, 'green',
|
||||||
|
'#ffe52c'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
expect(expressionToGlsl(context, expression)).to.eql(
|
||||||
|
'mix(vec4(1.0, 1.0, 0.0, 0.5), (a_year == 2000.0 ? vec4(0.0, 0.5019607843137255, 0.0, 1.0) : vec4(1.0, 0.8980392156862745, 0.17254901960784313, 1.0)), pow(clamp((pow((mod((u_time + mix(0.0, 8.0, pow(clamp((a_year - 1850.0) / (2015.0 - 1850.0), 0.0, 1.0), 1.0))), 8.0) / 8.0), 0.5) - 0.0) / (1.0 - 0.0), 0.0, 1.0), 1.0))'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user