Compare commits
252 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b64b8af2ea | ||
|
|
be312c1616 | ||
|
|
c27974b3da | ||
|
|
d7f7cbbc75 | ||
|
|
c0546c5f07 | ||
|
|
1f3e3efe5d | ||
|
|
cde6417d62 | ||
|
|
4c673ce846 | ||
|
|
3a869ba7f6 | ||
|
|
5c212008de | ||
|
|
be51d0480c | ||
|
|
e89b6c0a3f | ||
|
|
4b340fc9bc | ||
|
|
81fdcd5a38 | ||
|
|
cbfbe08aaa | ||
|
|
fce42dc80f | ||
|
|
9ddca3739b | ||
|
|
83375e735c | ||
|
|
0e4c40e315 | ||
|
|
39f62f1fa2 | ||
|
|
eab8dcf7ba | ||
|
|
8a67c5dbe6 | ||
|
|
72ad10887e | ||
|
|
8c96fc8120 | ||
|
|
348a1d1a13 | ||
|
|
0b0d6ba637 | ||
|
|
6173b3259d | ||
|
|
02f2cb8a65 | ||
|
|
3a4fdbdf6b | ||
|
|
593ea6706a | ||
|
|
0ac661bed1 | ||
|
|
aa7ab1ba65 | ||
|
|
d63728d3d4 | ||
|
|
c9ec6138ba | ||
|
|
c0bfcfb7ba | ||
|
|
69ec9871e2 | ||
|
|
1f40bcd633 | ||
|
|
5901751f43 | ||
|
|
7b8d6c817b | ||
|
|
0c912b6c29 | ||
|
|
3004f5707f | ||
|
|
c8067bebbb | ||
|
|
35ed7d4358 | ||
|
|
c76e78d0b3 | ||
|
|
ac4e472353 | ||
|
|
7168a26cab | ||
|
|
12795e3923 | ||
|
|
d8baa87e25 | ||
|
|
58cf9f5f6d | ||
|
|
4099f60779 | ||
|
|
791add0d73 | ||
|
|
d7af546ad3 | ||
|
|
d17c7ad31e | ||
|
|
95bfe85dd5 | ||
|
|
167d852e02 | ||
|
|
7bb5211fc0 | ||
|
|
04da7d568f | ||
|
|
df493725c6 | ||
|
|
25c1fe7d57 | ||
|
|
706955dfd9 | ||
|
|
611c0e5d6b | ||
|
|
ae38ef32df | ||
|
|
f5fc891ae0 | ||
|
|
1601d6b859 | ||
|
|
9c5d0b54e3 | ||
|
|
d37e3e3134 | ||
|
|
14371a5462 | ||
|
|
5e6e2f0fb6 | ||
|
|
64ea2a56da | ||
|
|
b588ec8c7f | ||
|
|
de9ff20f65 | ||
|
|
3b6bf14cdc | ||
|
|
d58a1b7d09 | ||
|
|
0fcaad321c | ||
|
|
fe9a8d2c74 | ||
|
|
64fff348fd | ||
|
|
88810afac6 | ||
|
|
1723bea12c | ||
|
|
fbded2a504 | ||
|
|
9ea56cf9c7 | ||
|
|
674ed45888 | ||
|
|
8674c46899 | ||
|
|
0a7c90acdd | ||
|
|
b71667c386 | ||
|
|
d85990436d | ||
|
|
3c1e3dcfd5 | ||
|
|
207fc99295 | ||
|
|
72d153696a | ||
|
|
a0b75268ee | ||
|
|
42970915ea | ||
|
|
8954f001fa | ||
|
|
b8c52b4298 | ||
|
|
10989f96bf | ||
|
|
814d70b1cc | ||
|
|
7acd5338c9 | ||
|
|
324148c606 | ||
|
|
7a8c1f309b | ||
|
|
011c14c7df | ||
|
|
283aed2dc9 | ||
|
|
4526f2ef34 | ||
|
|
efae01e71f | ||
|
|
394873013c | ||
|
|
e7dfcc77ae | ||
|
|
e8ead306ff | ||
|
|
7f3f4e6cdd | ||
|
|
79b3bc4244 | ||
|
|
05e0fb1bf7 | ||
|
|
f0cac76718 | ||
|
|
05eac3e384 | ||
|
|
311247265b | ||
|
|
fd43b00118 | ||
|
|
976f1b694a | ||
|
|
429a8fbc1a | ||
|
|
af80477c1d | ||
|
|
2dd212cdac | ||
|
|
6df5ff8d2b | ||
|
|
0f56eed272 | ||
|
|
b41abcb08e | ||
|
|
e9607acacb | ||
|
|
c155850395 | ||
|
|
b068284d8f | ||
|
|
2332acc4e4 | ||
|
|
d6a1f83b0e | ||
|
|
492fa3d9f7 | ||
|
|
2c0fc5eded | ||
|
|
82e17ca11b | ||
|
|
f5fd052aee | ||
|
|
d824475305 | ||
|
|
78c105e838 | ||
|
|
000461993b | ||
|
|
44ec78749f | ||
|
|
0649f613ad | ||
|
|
6ee0da2d76 | ||
|
|
d86cc70e46 | ||
|
|
91a24d45bd | ||
|
|
99fe1d1f8b | ||
|
|
5e50a23bc7 | ||
|
|
9f7b8b955f | ||
|
|
c328d47074 | ||
|
|
3420eeca1c | ||
|
|
1eb2f36deb | ||
|
|
1feae26489 | ||
|
|
3ad04d09cc | ||
|
|
acd6f9b5d7 | ||
|
|
f166629dd5 | ||
|
|
0d3526976d | ||
|
|
3bff0f23ca | ||
|
|
08583b4a41 | ||
|
|
73bb89f196 | ||
|
|
22a48fe939 | ||
|
|
2bcd57193d | ||
|
|
c1f4749e4d | ||
|
|
687219a089 | ||
|
|
f636d68cce | ||
|
|
d6e0eb75fa | ||
|
|
d7b443bf44 | ||
|
|
cebaa546b7 | ||
|
|
f8ac74fa1b | ||
|
|
aea11b773c | ||
|
|
022928eaa1 | ||
|
|
dbcb5d1582 | ||
|
|
6ce7b58020 | ||
|
|
7a2b0cd2d9 | ||
|
|
d938a75b53 | ||
|
|
7bb35422a0 | ||
|
|
27d915aefa | ||
|
|
4f38dd2c66 | ||
|
|
e9791cf4d3 | ||
|
|
40f7140348 | ||
|
|
c72a31a226 | ||
|
|
f0bbe366f5 | ||
|
|
68871eae86 | ||
|
|
632053a4f4 | ||
|
|
c197e80bd4 | ||
|
|
470b0df16a | ||
|
|
43a2ae29f0 | ||
|
|
e3777edac2 | ||
|
|
7fd51b3195 | ||
|
|
444da4ab39 | ||
|
|
4bdd40b7e1 | ||
|
|
8dc9ecd4b5 | ||
|
|
0c919784ae | ||
|
|
a8e890ca3f | ||
|
|
9f92628212 | ||
|
|
36fc2c042a | ||
|
|
f701f20eae | ||
|
|
6bc60f9911 | ||
|
|
ea9b2c4c41 | ||
|
|
2038a93880 | ||
|
|
0f894a28df | ||
|
|
0cc533e5d8 | ||
|
|
687b131714 | ||
|
|
fec6fee83c | ||
|
|
25af938a83 | ||
|
|
db57d9bf89 | ||
|
|
1dc336e459 | ||
|
|
7749eaf953 | ||
|
|
f9362a0825 | ||
|
|
9a38a91816 | ||
|
|
de460cfb09 | ||
|
|
20240c549f | ||
|
|
9986e6d0c0 | ||
|
|
310eeea207 | ||
|
|
498721ff57 | ||
|
|
a86cfcc3be | ||
|
|
2c3851b541 | ||
|
|
55fb897f88 | ||
|
|
8baba070d5 | ||
|
|
14eaaac976 | ||
|
|
16f8e1be7a | ||
|
|
9a883f5a73 | ||
|
|
a9b9bb34fa | ||
|
|
b05d4d9cad | ||
|
|
e50d5a2197 | ||
|
|
0b941e01d9 | ||
|
|
65742a22f9 | ||
|
|
2e054aea4d | ||
|
|
bf9ca7f3d1 | ||
|
|
82956aaf5e | ||
|
|
eb39f59713 | ||
|
|
a5a156525f | ||
|
|
a26fe5f31b | ||
|
|
8004e0faad | ||
|
|
c640457501 | ||
|
|
adff6f0e77 | ||
|
|
58b7faed55 | ||
|
|
48321a47d7 | ||
|
|
dc93933973 | ||
|
|
695fee9d3c | ||
|
|
9c2852bb7c | ||
|
|
408dace5c9 | ||
|
|
a32113afbf | ||
|
|
a81a2120e3 | ||
|
|
8b3494a585 | ||
|
|
822b967a9c | ||
|
|
f1530364dd | ||
|
|
19fb51183e | ||
|
|
6c862df03f | ||
|
|
431beed45f | ||
|
|
e8047f6d08 | ||
|
|
1b9329ea91 | ||
|
|
a617ef3caf | ||
|
|
8f8027c33a | ||
|
|
5d7f7dbf51 | ||
|
|
10e38947cc | ||
|
|
94d0b85fde | ||
|
|
5dd9836546 | ||
|
|
9f5da8cd76 | ||
|
|
1b2512d013 | ||
|
|
e1b161bbbe | ||
|
|
a8f7d955af | ||
|
|
9fd25dddbf |
@@ -1,6 +1,8 @@
|
||||
## Upgrade notes
|
||||
|
||||
### Next version
|
||||
### v6.7.0
|
||||
|
||||
There should be nothing special required when upgrading from v6.6 to v6.7.
|
||||
|
||||
### v6.6.0
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 6.4.1
|
||||
# 6.6.1
|
||||
|
||||
This is a bugfix release which brings improvements to the included TypeScript types, and fixes two minor issues with the Draw interaction and hit detection of regular shape symbols.
|
||||
|
||||
|
||||
121
changelog/v6.7.0.md
Normal file
121
changelog/v6.7.0.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# 6.7.0
|
||||
|
||||
The 6.7 release includes a great batch of usability improvements, fixes, and new features. See the full list of changes from 100 pull requests below, but here are some highlights:
|
||||
|
||||
* New GeoTIFF source! With parsing support from the awesome [geotiff.js](https://geotiffjs.github.io/) library, you can now render layers from hosted GeoTIFF imagery. The GeoTIFF source gives you the ability to pull from multiple GeoTIFF images, read from arbitrary bands, run band math expressions, and style the imagery to your liking.
|
||||
* New WebGL tile renderer. The GeoTIFF source is rendered with a new WebGL-based tile renderer. In addition to GeoTIFFs, the renderer supports layers with a generic DataTile source – these can be used to render aribtrary raster data and leverage the same style expressions as described above.
|
||||
* More type checking. We continue to make improvements to the TypeScript definitions included in the `ol` package.
|
||||
* New sources supporting the draft [OGC API - Tiles](https://ogcapi.ogc.org/tiles/) specification. The OGCMapTile and OGCVectorTile sources allow you to render data from services that implement the draft OGC tiles spec. Since the specification is not yet final, these sources are not yet part of the stable OpenLayers API and should be considered experimental.
|
||||
* Custom cluster creation support, improved KML icon rendering, lots of fixes, and more. See below for all the detail.
|
||||
|
||||
## List of all changes
|
||||
|
||||
* [#12727](https://github.com/openlayers/openlayers/pull/12727) - Add missing 'boxstart' event to OnSignature ([@simonseyock](https://github.com/simonseyock))
|
||||
* [#12718](https://github.com/openlayers/openlayers/pull/12718) - Rename function to avoid minification failure ([@tschaub](https://github.com/tschaub))
|
||||
* [#12712](https://github.com/openlayers/openlayers/pull/12712) - Add convertToRGB option to GeoTIFF source ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12716](https://github.com/openlayers/openlayers/pull/12716) - Spelling fix ([@fredj](https://github.com/fredj))
|
||||
* [#12715](https://github.com/openlayers/openlayers/pull/12715) - Try harder to get the projection from GeoTIFF headers ([@tschaub](https://github.com/tschaub))
|
||||
* [#12714](https://github.com/openlayers/openlayers/pull/12714) - Avoid creating duplicate projections in COG examples ([@tschaub](https://github.com/tschaub))
|
||||
* [#12713](https://github.com/openlayers/openlayers/pull/12713) - Check for GeoTIFF CRS starting with last image ([@tschaub](https://github.com/tschaub))
|
||||
* [#12709](https://github.com/openlayers/openlayers/pull/12709) - Support rendering of GeoTIFF images in pixel coordinates ([@tschaub](https://github.com/tschaub))
|
||||
* [#12711](https://github.com/openlayers/openlayers/pull/12711) - Use band numbers starting with one ([@tschaub](https://github.com/tschaub))
|
||||
* [#12710](https://github.com/openlayers/openlayers/pull/12710) - Spelling fix in GeoTIFF docs ([@tschaub](https://github.com/tschaub))
|
||||
* [#12697](https://github.com/openlayers/openlayers/pull/12697) - Move description above type tag ([@tschaub](https://github.com/tschaub))
|
||||
* [#12695](https://github.com/openlayers/openlayers/pull/12695) - Update Google KML icon anchors and correct icon scaling ([@mike-000](https://github.com/mike-000))
|
||||
* [#12642](https://github.com/openlayers/openlayers/pull/12642) - Fix fullscreen in mapbox-style example ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12624](https://github.com/openlayers/openlayers/pull/12624) - Improve API docs for optional properties ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#10963](https://github.com/openlayers/openlayers/pull/10963) - OGC map and vector tile sources ([@tschaub](https://github.com/tschaub))
|
||||
* [#12690](https://github.com/openlayers/openlayers/pull/12690) - Fix return stride of forEachSegment ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12684](https://github.com/openlayers/openlayers/pull/12684) - Fix view resolutions in example ([@mike-000](https://github.com/mike-000))
|
||||
* [#12683](https://github.com/openlayers/openlayers/pull/12683) - Fix sprite offset for pixel ratio !== 1 ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12671](https://github.com/openlayers/openlayers/pull/12671) - Configure cache on the layer instead of the source ([@tschaub](https://github.com/tschaub))
|
||||
* [#12669](https://github.com/openlayers/openlayers/pull/12669) - Add opaque and transition options to GeoTIFF source ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12667](https://github.com/openlayers/openlayers/pull/12667) - Additional docs and type checking for raster source ([@tschaub](https://github.com/tschaub))
|
||||
* [#12666](https://github.com/openlayers/openlayers/pull/12666) - Re-add accidently removed events ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12647](https://github.com/openlayers/openlayers/pull/12647) - Remove Translate option conflicts and update documentation ([@mike-000](https://github.com/mike-000))
|
||||
* [#12008](https://github.com/openlayers/openlayers/pull/12008) - Rendering raster tiles with WebGL ([@tschaub](https://github.com/tschaub))
|
||||
* [#12632](https://github.com/openlayers/openlayers/pull/12632) - Only warn of zero size when map should be visible ([@mike-000](https://github.com/mike-000))
|
||||
* [#12626](https://github.com/openlayers/openlayers/pull/12626) - Set canvas style to override problem 3rd party css ([@mike-000](https://github.com/mike-000))
|
||||
* [#12608](https://github.com/openlayers/openlayers/pull/12608) - Support more OGC CRS identifiers ([@tschaub](https://github.com/tschaub))
|
||||
* [#12607](https://github.com/openlayers/openlayers/pull/12607) - Don't add color as possible type if it was not set ([@MoonE](https://github.com/MoonE))
|
||||
* [#12605](https://github.com/openlayers/openlayers/pull/12605) - Some typing improvements ([@simonseyock](https://github.com/simonseyock))
|
||||
* [#12600](https://github.com/openlayers/openlayers/pull/12600) - Make attribution getters public. ([@simonseyock](https://github.com/simonseyock))
|
||||
* [#12597](https://github.com/openlayers/openlayers/pull/12597) - #12596 fix RasterSource does not end Tile transition ([@mwerlitz](https://github.com/mwerlitz))
|
||||
* [#12599](https://github.com/openlayers/openlayers/pull/12599) - Replace reference image to match that of the CI ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12595](https://github.com/openlayers/openlayers/pull/12595) - Generate correct type definitions with null ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12578](https://github.com/openlayers/openlayers/pull/12578) - Update rendering test reference image ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12577](https://github.com/openlayers/openlayers/pull/12577) - Use shx for the build-site script, clean before run ([@MoonE](https://github.com/MoonE))
|
||||
* [#12575](https://github.com/openlayers/openlayers/pull/12575) - Add on(), un() and once() signatures for ol/source/Raster ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12565](https://github.com/openlayers/openlayers/pull/12565) - Handle named colors as string in equal operator ([@sebakerckhof](https://github.com/sebakerckhof))
|
||||
* [#12576](https://github.com/openlayers/openlayers/pull/12576) - Fix example and legacy build with nodejs 16.6 ([@MoonE](https://github.com/MoonE))
|
||||
* [#12551](https://github.com/openlayers/openlayers/pull/12551) - Add WKB to the exports of ol/format ([@M393](https://github.com/M393))
|
||||
* [#12550](https://github.com/openlayers/openlayers/pull/12550) - Add `grid` tag to examples. ([@simonseyock](https://github.com/simonseyock))
|
||||
* [#12549](https://github.com/openlayers/openlayers/pull/12549) - Add `change:layers` event. ([@simonseyock](https://github.com/simonseyock))
|
||||
* [#12545](https://github.com/openlayers/openlayers/pull/12545) - Restore simple axis order handling with fixed proj4 ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12544](https://github.com/openlayers/openlayers/pull/12544) - Make Raster source work as generics type for Image layer ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12527](https://github.com/openlayers/openlayers/pull/12527) - Treat custom loaders without success/fail handling as if they were a void loader ([@mike-000](https://github.com/mike-000))
|
||||
* [#12538](https://github.com/openlayers/openlayers/pull/12538) - Avoid creating context until needed ([@tschaub](https://github.com/tschaub))
|
||||
* [#12528](https://github.com/openlayers/openlayers/pull/12528) - Convert the hit tolerance priority example into a test ([@MoonE](https://github.com/MoonE))
|
||||
* [#12522](https://github.com/openlayers/openlayers/pull/12522) - Shorter name for the option to create a custom cluster ([@tschaub](https://github.com/tschaub))
|
||||
* [#12506](https://github.com/openlayers/openlayers/pull/12506) - Fix adding controls with map config ([@M393](https://github.com/M393))
|
||||
* [#12487](https://github.com/openlayers/openlayers/pull/12487) - Custom cluster feature creation function ([@Razi91](https://github.com/Razi91))
|
||||
* [#12520](https://github.com/openlayers/openlayers/pull/12520) - Fix typo in changelog ([@ahocevar](https://github.com/ahocevar))
|
||||
* [#12519](https://github.com/openlayers/openlayers/pull/12519) - Release v6.6.1 ([@openlayers](https://github.com/openlayers))
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Dependency Updates</summary>
|
||||
|
||||
* [#12703](https://github.com/openlayers/openlayers/pull/12703) - Bump terser-webpack-plugin from 5.1.4 to 5.2.3 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12704](https://github.com/openlayers/openlayers/pull/12704) - Bump @babel/eslint-parser from 7.15.0 to 7.15.4 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12705](https://github.com/openlayers/openlayers/pull/12705) - Bump webpack from 5.51.1 to 5.52.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12706](https://github.com/openlayers/openlayers/pull/12706) - Bump webpack-dev-server from 4.0.0 to 4.1.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12707](https://github.com/openlayers/openlayers/pull/12707) - Bump @babel/core from 7.15.0 to 7.15.5 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12702](https://github.com/openlayers/openlayers/pull/12702) - Bump @babel/preset-env from 7.15.0 to 7.15.4 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12677](https://github.com/openlayers/openlayers/pull/12677) - Bump marked from 3.0.0 to 3.0.2 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12678](https://github.com/openlayers/openlayers/pull/12678) - Bump globby from 12.0.1 to 12.0.2 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12679](https://github.com/openlayers/openlayers/pull/12679) - Bump mocha from 9.1.0 to 9.1.1 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12657](https://github.com/openlayers/openlayers/pull/12657) - Bump mocha from 9.0.3 to 9.1.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12650](https://github.com/openlayers/openlayers/pull/12650) - Bump webpack from 5.50.0 to 5.51.1 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12654](https://github.com/openlayers/openlayers/pull/12654) - Bump webpack-dev-server from 4.0.0-rc.0 to 4.0.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12653](https://github.com/openlayers/openlayers/pull/12653) - Bump globby from 12.0.0 to 12.0.1 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12652](https://github.com/openlayers/openlayers/pull/12652) - Bump loglevelnext from 5.0.5 to 5.0.6 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12651](https://github.com/openlayers/openlayers/pull/12651) - Bump rollup from 2.56.2 to 2.56.3 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12627](https://github.com/openlayers/openlayers/pull/12627) - Bump webpack-cli from 4.7.2 to 4.8.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12631](https://github.com/openlayers/openlayers/pull/12631) - Bump marked from 2.1.3 to 3.0.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12630](https://github.com/openlayers/openlayers/pull/12630) - Bump webpack from 5.49.0 to 5.50.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12629](https://github.com/openlayers/openlayers/pull/12629) - Bump yargs from 17.1.0 to 17.1.1 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12628](https://github.com/openlayers/openlayers/pull/12628) - Bump rollup from 2.56.1 to 2.56.2 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12616](https://github.com/openlayers/openlayers/pull/12616) - Bump webpack from 5.47.1 to 5.49.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12617](https://github.com/openlayers/openlayers/pull/12617) - Bump @babel/eslint-parser from 7.14.9 to 7.15.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12615](https://github.com/openlayers/openlayers/pull/12615) - Bump rollup from 2.56.0 to 2.56.1 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12614](https://github.com/openlayers/openlayers/pull/12614) - Bump puppeteer from 10.1.0 to 10.2.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12613](https://github.com/openlayers/openlayers/pull/12613) - Bump yargs from 17.0.1 to 17.1.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12612](https://github.com/openlayers/openlayers/pull/12612) - Bump @babel/core from 7.14.8 to 7.15.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12611](https://github.com/openlayers/openlayers/pull/12611) - Bump @babel/preset-env from 7.14.9 to 7.15.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12610](https://github.com/openlayers/openlayers/pull/12610) - Bump clean-css-cli from 5.3.2 to 5.3.3 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12602](https://github.com/openlayers/openlayers/pull/12602) - Bump rollup from 2.54.0 to 2.56.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12583](https://github.com/openlayers/openlayers/pull/12583) - Bump webpack-sources from 2.3.1 to 3.2.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12581](https://github.com/openlayers/openlayers/pull/12581) - Bump @rollup/plugin-commonjs from 19.0.1 to 20.0.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12582](https://github.com/openlayers/openlayers/pull/12582) - Bump @babel/preset-env from 7.14.8 to 7.14.9 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12585](https://github.com/openlayers/openlayers/pull/12585) - Bump clean-css-cli from 5.3.0 to 5.3.2 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12584](https://github.com/openlayers/openlayers/pull/12584) - Bump webpack from 5.46.0 to 5.47.1 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12586](https://github.com/openlayers/openlayers/pull/12586) - Bump @babel/eslint-parser from 7.14.7 to 7.14.9 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12588](https://github.com/openlayers/openlayers/pull/12588) - Bump sinon from 11.1.1 to 11.1.2 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12589](https://github.com/openlayers/openlayers/pull/12589) - Bump eslint from 7.31.0 to 7.32.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12559](https://github.com/openlayers/openlayers/pull/12559) - Bump rollup from 2.53.2 to 2.54.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12560](https://github.com/openlayers/openlayers/pull/12560) - Bump @babel/core from 7.14.6 to 7.14.8 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12558](https://github.com/openlayers/openlayers/pull/12558) - Bump webpack-dev-server from 4.0.0-beta.3 to 4.0.0-rc.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12556](https://github.com/openlayers/openlayers/pull/12556) - Bump globby from 11.0.4 to 12.0.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12555](https://github.com/openlayers/openlayers/pull/12555) - Bump webpack from 5.45.1 to 5.46.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12554](https://github.com/openlayers/openlayers/pull/12554) - Bump mocha from 9.0.2 to 9.0.3 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12553](https://github.com/openlayers/openlayers/pull/12553) - Bump @babel/preset-env from 7.14.7 to 7.14.8 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12552](https://github.com/openlayers/openlayers/pull/12552) - Bump @rollup/plugin-node-resolve from 13.0.2 to 13.0.4 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12536](https://github.com/openlayers/openlayers/pull/12536) - Bump rollup from 2.53.1 to 2.53.2 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12537](https://github.com/openlayers/openlayers/pull/12537) - Bump @rollup/plugin-node-resolve from 13.0.0 to 13.0.2 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12535](https://github.com/openlayers/openlayers/pull/12535) - Bump webpack from 5.44.0 to 5.45.1 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12534](https://github.com/openlayers/openlayers/pull/12534) - Bump eslint from 7.30.0 to 7.31.0 ([@openlayers](https://github.com/openlayers))
|
||||
* [#12533](https://github.com/openlayers/openlayers/pull/12533) - Bump @rollup/plugin-commonjs from 19.0.0 to 19.0.1 ([@openlayers](https://github.com/openlayers))
|
||||
|
||||
|
||||
</details>
|
||||
@@ -27,7 +27,8 @@
|
||||
<a href="module-ol_layer_Tile-TileLayer.html">ol/layer/Tile</a><br>
|
||||
<a href="module-ol_layer_Image-ImageLayer.html">ol/layer/Image</a><br>
|
||||
<a href="module-ol_layer_Vector-VectorLayer.html">ol/layer/Vector</a><br>
|
||||
<a href="module-ol_layer_VectorTile-VectorTileLayer.html">ol/layer/VectorTile</a>
|
||||
<a href="module-ol_layer_VectorTile-VectorTileLayer.html">ol/layer/VectorTile</a><br>
|
||||
<a href="module-ol_layer_WebGLTile-WebGLTileLayer.html">ol/layer/WebGLTile</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,7 +59,7 @@
|
||||
<div class="card h-100 bg-light">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Sources and formats</h4>
|
||||
<a href="module-ol_source_Tile-TileSource.html">Tile sources</a> for <a href="module-ol_layer_Tile-TileLayer.html">ol/layer/Tile</a>
|
||||
<a href="module-ol_source_Tile-TileSource.html">Tile sources</a> for <a href="module-ol_layer_Tile-TileLayer.html">ol/layer/Tile</a> or <a href="module-ol_layer_WebGLTile-WebGLTileLayer.html">ol/layer/WebGLTile</a>
|
||||
<br><a href="module-ol_source_Image-ImageSource.html">Image sources</a> for <a href="module-ol_layer_Image-ImageLayer.html">ol/layer/Image</a>
|
||||
<br><a href="module-ol_source_Vector-VectorSource.html">Vector sources</a> for <a href="module-ol_layer_Vector-VectorLayer.html">ol/layer/Vector</a>
|
||||
<br><a href="module-ol_source_VectorTile-VectorTile.html">Vector tile sources</a> for <a href="module-ol_layer_VectorTile-VectorTileLayer.html">ol/layer/VectorTile</a>
|
||||
@@ -71,7 +72,7 @@
|
||||
<div class="card h-100 bg-light">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Projections</h4>
|
||||
<p>All coordinates and extents need to be provided in view projection (default: EPSG:3857). To transform, use <a href="module-ol_proj.html#.transform">ol/proj#transform()</a> and <a href="module-ol_proj.html#.transformExtent">ol/proj#transformExtent()</a>.</p>
|
||||
<p>All coordinates and extents need to be provided in view projection (default: EPSG:3857). To transform coordinates from and to geographic, use <a href="module-ol_proj.html#.fromLonLat">ol/proj#fromLonLat()</a> and <a href="module-ol_proj.html#.toLonLat">ol/proj#toLonLat()</a>. For extents and other projections, use <a href="module-ol_proj.html#.transformExtent">ol/proj#transformExtent()</a> and <a href="module-ol_proj.html#.transform">ol/proj#transform()</a>.<p>
|
||||
<a href="module-ol_proj.html">ol/proj</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<?js if (!param.subparams) {?>
|
||||
<td class="type">
|
||||
<?js if (param.type && param.type.names) {?>
|
||||
<?js= self.partial('type.tmpl', param.type.names) ?>
|
||||
<?js= self.partial('type.tmpl', param.type.names) + (param.optional && typeof param.defaultvalue === 'undefined' && param.type.names.indexOf('undefined') === -1 ? ' | undefined' : '') ?>
|
||||
<?js if (typeof param.defaultvalue !== 'undefined') { ?>
|
||||
(defaults to <?js= self.htmlsafe(param.defaultvalue) ?>)
|
||||
<?js } ?>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": false, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
"strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
|
||||
@@ -32,6 +32,11 @@ export default {
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
fallback: {
|
||||
fs: false,
|
||||
http: false,
|
||||
https: false,
|
||||
},
|
||||
alias: {
|
||||
ol: path.resolve('./build/ol'),
|
||||
},
|
||||
13
examples/cog-math-multisource.html
Normal file
13
examples/cog-math-multisource.html
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: NDVI+NDWI from two 16-bit COGs
|
||||
shortdesc: Calculating NDVI+NDWI as green and blue values.
|
||||
docs: >
|
||||
The GeoTIFF layer in this example calculates the Normalized Difference Vegetation Index (NDVI)
|
||||
and Normalized Difference Water Index (NDWI) from two cloud-optimized Sentinel 2 GeoTIFFs: one
|
||||
with 10 m resolution and red and a near infrared bands, and one with 60 m resolution and a short
|
||||
wave infrared channel. The NDVI is shown as green, the NDWI as blue. The 4th band is the alpha
|
||||
band, which gets added when a source has a `nodata` value configured.
|
||||
tags: "cog, ndvi, ndwi, sentinel, geotiff"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
65
examples/cog-math-multisource.js
Normal file
65
examples/cog-math-multisource.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import GeoTIFF from '../src/ol/source/GeoTIFF.js';
|
||||
import Map from '../src/ol/Map.js';
|
||||
import TileLayer from '../src/ol/layer/WebGLTile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
|
||||
const source = new GeoTIFF({
|
||||
sources: [
|
||||
{
|
||||
url: 'https://s2downloads.eox.at/demo/Sentinel-2/3857/R10m.tif',
|
||||
bands: [3, 4],
|
||||
min: 0,
|
||||
nodata: 0,
|
||||
max: 65535,
|
||||
},
|
||||
{
|
||||
url: 'https://s2downloads.eox.at/demo/Sentinel-2/3857/R60m.tif',
|
||||
bands: [9],
|
||||
min: 0,
|
||||
nodata: 0,
|
||||
max: 65535,
|
||||
},
|
||||
],
|
||||
});
|
||||
source.setAttributions(
|
||||
"<a href='https://s2maps.eu'>Sentinel-2 cloudless</a> by <a href='https://eox.at/'>EOX IT Services GmbH</a> (Contains modified Copernicus Sentinel data 2019)"
|
||||
);
|
||||
|
||||
const ndvi = [
|
||||
'/',
|
||||
['-', ['band', 2], ['band', 1]],
|
||||
['+', ['band', 2], ['band', 1]],
|
||||
];
|
||||
|
||||
const ndwi = [
|
||||
'/',
|
||||
['-', ['band', 3], ['band', 1]],
|
||||
['+', ['band', 3], ['band', 1]],
|
||||
];
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new TileLayer({
|
||||
style: {
|
||||
color: [
|
||||
'color',
|
||||
// red: | NDVI - NDWI |
|
||||
['*', 255, ['abs', ['-', ndvi, ndwi]]],
|
||||
// green: NDVI
|
||||
['*', 255, ndvi],
|
||||
// blue: NDWI
|
||||
['*', 255, ndwi],
|
||||
// alpha
|
||||
['band', 4],
|
||||
],
|
||||
},
|
||||
source,
|
||||
}),
|
||||
],
|
||||
view: new View({
|
||||
center: [1900000, 6100000],
|
||||
zoom: 13,
|
||||
minZoom: 10,
|
||||
}),
|
||||
});
|
||||
11
examples/cog-math.html
Normal file
11
examples/cog-math.html
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: NDVI from a Sentinel 2 COG
|
||||
shortdesc: Calculating NDVI and applying a custom color map.
|
||||
docs: >
|
||||
The GeoTIFF layer in this example draws from two Sentinel 2 sources: a red band and a near infrared band.
|
||||
The layer style includes a `color` expression that calculates the Normalized Difference Vegetation Index (NDVI)
|
||||
from values in the two bands. The `interpolate` expression is used to map NDVI values to colors.
|
||||
tags: "cog, ndvi"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
95
examples/cog-math.js
Normal file
95
examples/cog-math.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import GeoTIFF from '../src/ol/source/GeoTIFF.js';
|
||||
import Map from '../src/ol/Map.js';
|
||||
import TileLayer from '../src/ol/layer/WebGLTile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
import proj4 from 'proj4';
|
||||
import {getCenter} from '../src/ol/extent.js';
|
||||
import {register} from '../src/ol/proj/proj4.js';
|
||||
|
||||
proj4.defs('EPSG:32636', '+proj=utm +zone=36 +datum=WGS84 +units=m +no_defs');
|
||||
register(proj4);
|
||||
|
||||
// metadata from https://s3.us-west-2.amazonaws.com/sentinel-cogs/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/S2A_36QWD_20200701_0_L2A.json
|
||||
const sourceExtent = [499980, 1790220, 609780, 1900020];
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new TileLayer({
|
||||
style: {
|
||||
color: [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
// calculate NDVI, bands come from the sources below
|
||||
[
|
||||
'/',
|
||||
['-', ['band', 2], ['band', 1]],
|
||||
['+', ['band', 2], ['band', 1]],
|
||||
],
|
||||
// color ramp for NDVI values, ranging from -1 to 1
|
||||
-0.2,
|
||||
[191, 191, 191],
|
||||
-0.1,
|
||||
[219, 219, 219],
|
||||
0,
|
||||
[255, 255, 224],
|
||||
0.025,
|
||||
[255, 250, 204],
|
||||
0.05,
|
||||
[237, 232, 181],
|
||||
0.075,
|
||||
[222, 217, 156],
|
||||
0.1,
|
||||
[204, 199, 130],
|
||||
0.125,
|
||||
[189, 184, 107],
|
||||
0.15,
|
||||
[176, 194, 97],
|
||||
0.175,
|
||||
[163, 204, 89],
|
||||
0.2,
|
||||
[145, 191, 82],
|
||||
0.25,
|
||||
[128, 179, 71],
|
||||
0.3,
|
||||
[112, 163, 64],
|
||||
0.35,
|
||||
[97, 150, 54],
|
||||
0.4,
|
||||
[79, 138, 46],
|
||||
0.45,
|
||||
[64, 125, 36],
|
||||
0.5,
|
||||
[48, 110, 28],
|
||||
0.55,
|
||||
[33, 97, 18],
|
||||
0.6,
|
||||
[15, 84, 10],
|
||||
0.65,
|
||||
[0, 69, 0],
|
||||
],
|
||||
},
|
||||
source: new GeoTIFF({
|
||||
sources: [
|
||||
{
|
||||
// visible red, band 1 in the style expression above
|
||||
url: 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/B04.tif',
|
||||
max: 10000,
|
||||
},
|
||||
{
|
||||
// near infrared, band 2 in the style expression above
|
||||
url: 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/B08.tif',
|
||||
max: 10000,
|
||||
},
|
||||
],
|
||||
}),
|
||||
extent: sourceExtent,
|
||||
}),
|
||||
],
|
||||
view: new View({
|
||||
projection: 'EPSG:32636',
|
||||
center: getCenter(sourceExtent),
|
||||
extent: sourceExtent,
|
||||
zoom: 9,
|
||||
}),
|
||||
});
|
||||
12
examples/cog-overviews.html
Normal file
12
examples/cog-overviews.html
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: GeoTIFF with Overviews
|
||||
shortdesc: Rendering a GeoTIFF with external overviews as a layer.
|
||||
docs: >
|
||||
In some cases, a GeoTIFF may have external overviews. This example uses the
|
||||
`overviews` property to provide URLs for the external overviews. The example
|
||||
composes a false color composite using shortwave infrared (B6), near infrared (B5),
|
||||
and visible green (B3) bands from a Landsat 8 image.
|
||||
tags: "cog"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
62
examples/cog-overviews.js
Normal file
62
examples/cog-overviews.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import GeoTIFF from '../src/ol/source/GeoTIFF.js';
|
||||
import Map from '../src/ol/Map.js';
|
||||
import TileLayer from '../src/ol/layer/WebGLTile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
import proj4 from 'proj4';
|
||||
import {getCenter} from '../src/ol/extent.js';
|
||||
import {register} from '../src/ol/proj/proj4.js';
|
||||
|
||||
proj4.defs('EPSG:32645', '+proj=utm +zone=45 +datum=WGS84 +units=m +no_defs');
|
||||
register(proj4);
|
||||
|
||||
const sourceExtent = [382200, 2279370, 610530, 2512500];
|
||||
|
||||
const base =
|
||||
'https://landsat-pds.s3.amazonaws.com/c1/L8/139/045/LC08_L1TP_139045_20170304_20170316_01_T1/LC08_L1TP_139045_20170304_20170316_01_T1';
|
||||
|
||||
// scale values in this range to 0 - 1
|
||||
const min = 10000;
|
||||
const max = 15000;
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new TileLayer({
|
||||
extent: sourceExtent,
|
||||
style: {
|
||||
saturation: -0.3,
|
||||
},
|
||||
source: new GeoTIFF({
|
||||
sources: [
|
||||
{
|
||||
url: `${base}_B6.TIF`,
|
||||
overviews: [`${base}_B6.TIF.ovr`],
|
||||
min: min,
|
||||
max: max,
|
||||
nodata: 0,
|
||||
},
|
||||
{
|
||||
url: `${base}_B5.TIF`,
|
||||
overviews: [`${base}_B5.TIF.ovr`],
|
||||
min: min,
|
||||
max: max,
|
||||
nodata: 0,
|
||||
},
|
||||
{
|
||||
url: `${base}_B3.TIF`,
|
||||
overviews: [`${base}_B3.TIF.ovr`],
|
||||
min: min,
|
||||
max: max,
|
||||
nodata: 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
view: new View({
|
||||
projection: 'EPSG:32645',
|
||||
center: getCenter(sourceExtent),
|
||||
extent: sourceExtent,
|
||||
zoom: 8,
|
||||
}),
|
||||
});
|
||||
12
examples/cog-pyramid.html
Normal file
12
examples/cog-pyramid.html
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: GeoTIFF tile pyramid
|
||||
shortdesc: Rendering a COG tile pyramid as layer group.
|
||||
docs: >
|
||||
Data from a Cloud Optimized GeoTIFF (COG) tile pyramid can be rendered as a set of layers. In this
|
||||
example, a pyramid of 3-band GeoTIFFs is used to render RGB data. For each tile of the pyramid, a
|
||||
separate layer is created on demand. The lowest resolution layer serves as preview while higher resolutions are
|
||||
loading.
|
||||
tags: "cog, tilepyramid, stac"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
71
examples/cog-pyramid.js
Normal file
71
examples/cog-pyramid.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import GeoTIFF from '../src/ol/source/GeoTIFF.js';
|
||||
import LayerGroup from '../src/ol/layer/Group.js';
|
||||
import Map from '../src/ol/Map.js';
|
||||
import TileGrid from '../src/ol/tilegrid/TileGrid.js';
|
||||
import View from '../src/ol/View.js';
|
||||
import WebGLTileLayer from '../src/ol/layer/WebGLTile.js';
|
||||
import {getIntersection} from '../src/ol/extent.js';
|
||||
|
||||
// Metadata from https://s2downloads.eox.at/demo/EOxCloudless/2019/rgb/2019_EOxCloudless_rgb.json
|
||||
|
||||
// Tile grid of the GeoTIFF pyramid layout
|
||||
const tileGrid = new TileGrid({
|
||||
origin: [-180, 90],
|
||||
resolutions: [0.703125, 0.3515625, 0.17578125, 8.7890625e-2, 4.39453125e-2],
|
||||
tileSizes: [
|
||||
[512, 256],
|
||||
[1024, 512],
|
||||
[2048, 1024],
|
||||
[4096, 2048],
|
||||
[4096, 4096],
|
||||
],
|
||||
});
|
||||
|
||||
const pyramid = new LayerGroup();
|
||||
const layerForUrl = {};
|
||||
const zs = tileGrid.getResolutions().length;
|
||||
|
||||
function useLayer(z, x, y) {
|
||||
const url = `https://s2downloads.eox.at/demo/EOxCloudless/2019/rgb/${z}/${y}/${x}.tif`;
|
||||
if (!(url in layerForUrl)) {
|
||||
pyramid.getLayers().push(
|
||||
new WebGLTileLayer({
|
||||
minZoom: z,
|
||||
maxZoom: z === 0 || z === zs - 1 ? undefined : z + 1,
|
||||
extent: tileGrid.getTileCoordExtent([z, x, y]),
|
||||
source: new GeoTIFF({
|
||||
sources: [
|
||||
{
|
||||
url: url,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
);
|
||||
layerForUrl[url] = true;
|
||||
}
|
||||
}
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [pyramid],
|
||||
view: new View({
|
||||
projection: 'EPSG:4326',
|
||||
center: [0, 0],
|
||||
zoom: 0,
|
||||
showFullExtent: true,
|
||||
}),
|
||||
});
|
||||
|
||||
// Add overview layer
|
||||
useLayer(0, 0, 0);
|
||||
|
||||
// Add layer for specific extent on demand
|
||||
map.on('moveend', () => {
|
||||
const view = map.getView();
|
||||
tileGrid.forEachTileCoord(
|
||||
getIntersection([-180, -90, 180, 90], view.calculateExtent()),
|
||||
tileGrid.getZForResolution(view.getResolution()),
|
||||
([z, x, y]) => useLayer(z, x, y)
|
||||
);
|
||||
});
|
||||
10
examples/cog.html
Normal file
10
examples/cog.html
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: Cloud Optimized GeoTIFF (COG)
|
||||
shortdesc: Rendering a COG as a tiled layer.
|
||||
docs: >
|
||||
Tiled data from a Cloud Optimized GeoTIFF (COG) can be rendered as a layer. In this
|
||||
example, a single 3-band GeoTIFF is used to render RGB data.
|
||||
tags: "cog"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
35
examples/cog.js
Normal file
35
examples/cog.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import GeoTIFF from '../src/ol/source/GeoTIFF.js';
|
||||
import Map from '../src/ol/Map.js';
|
||||
import TileLayer from '../src/ol/layer/WebGLTile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
import proj4 from 'proj4';
|
||||
import {getCenter} from '../src/ol/extent.js';
|
||||
import {register} from '../src/ol/proj/proj4.js';
|
||||
|
||||
proj4.defs('EPSG:32636', '+proj=utm +zone=36 +datum=WGS84 +units=m +no_defs');
|
||||
register(proj4);
|
||||
|
||||
// metadata from https://s3.us-west-2.amazonaws.com/sentinel-cogs/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/S2A_36QWD_20200701_0_L2A.json
|
||||
const sourceExtent = [499980, 1790220, 609780, 1900020];
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: new GeoTIFF({
|
||||
sources: [
|
||||
{
|
||||
url: 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/TCI.tif',
|
||||
},
|
||||
],
|
||||
}),
|
||||
extent: sourceExtent,
|
||||
}),
|
||||
],
|
||||
view: new View({
|
||||
projection: 'EPSG:32636',
|
||||
center: getCenter(sourceExtent),
|
||||
extent: sourceExtent,
|
||||
zoom: 9,
|
||||
}),
|
||||
});
|
||||
9
examples/data-tiles.html
Normal file
9
examples/data-tiles.html
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: Data Tiles
|
||||
shortdesc: Generating tile data from scratch.
|
||||
docs: >
|
||||
This example generates RGBA tile data from scratch.
|
||||
tags: "data tiles"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
45
examples/data-tiles.js
Normal file
45
examples/data-tiles.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import DataTile from '../src/ol/source/DataTile.js';
|
||||
import Map from '../src/ol/Map.js';
|
||||
import TileLayer from '../src/ol/layer/WebGLTile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
|
||||
const size = 256;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
context.strokeStyle = 'white';
|
||||
context.textAlign = 'center';
|
||||
context.font = '24px sans-serif';
|
||||
const lineHeight = 30;
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: new DataTile({
|
||||
loader: function (z, x, y) {
|
||||
const half = size / 2;
|
||||
context.clearRect(0, 0, size, size);
|
||||
context.fillStyle = 'rgba(100, 100, 100, 0.5)';
|
||||
context.fillRect(0, 0, size, size);
|
||||
context.fillStyle = 'black';
|
||||
context.fillText(`z: ${z}`, half, half - lineHeight);
|
||||
context.fillText(`x: ${x}`, half, half);
|
||||
context.fillText(`y: ${y}`, half, half + lineHeight);
|
||||
context.strokeRect(0, 0, size, size);
|
||||
const data = context.getImageData(0, 0, size, size).data;
|
||||
return Promise.resolve(data);
|
||||
},
|
||||
// disable opacity transition to avoid overlapping labels during tile loading
|
||||
transition: 0,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
view: new View({
|
||||
center: [0, 0],
|
||||
zoom: 0,
|
||||
}),
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: Hit tolerance priority
|
||||
shortdesc: Shows bad behavior of hit detection with hit tolerance.
|
||||
docs: >
|
||||
Hover over the map and observe how the small circles get a black outline as you hover over them. Is the expected feature getting highlighted?
|
||||
tags: "simple, openstreetmap"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
@@ -1,84 +0,0 @@
|
||||
import CircleStyle from '../src/ol/style/Circle.js';
|
||||
import Feature from '../src/ol/Feature.js';
|
||||
import Map from '../src/ol/Map.js';
|
||||
import VectorLayer from '../src/ol/layer/Vector.js';
|
||||
import VectorSource from '../src/ol/source/Vector.js';
|
||||
import View from '../src/ol/View.js';
|
||||
import {Fill, Stroke, Style} from '../src/ol/style.js';
|
||||
import {Point} from '../src/ol/geom.js';
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
view: new View({
|
||||
center: [0, 0],
|
||||
resolution: 1,
|
||||
resolutions: [1],
|
||||
}),
|
||||
});
|
||||
|
||||
const vectorLayer = new VectorLayer({
|
||||
source: new VectorSource({
|
||||
features: [
|
||||
new Feature({
|
||||
geometry: new Point([0, 0]),
|
||||
color: 'white',
|
||||
}),
|
||||
new Feature({
|
||||
geometry: new Point([-10, 0]),
|
||||
color: 'fuchsia',
|
||||
}),
|
||||
new Feature({
|
||||
geometry: new Point([-10, -10]),
|
||||
color: 'orange',
|
||||
}),
|
||||
new Feature({
|
||||
geometry: new Point([-10, 10]),
|
||||
color: 'cyan',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
style: (feature) => {
|
||||
return new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 5,
|
||||
fill: new Fill({
|
||||
color: feature.get('color'),
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: 'gray',
|
||||
width: 1,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
map.addLayer(vectorLayer);
|
||||
|
||||
const highlightFeature = new Feature(new Point([NaN, NaN]));
|
||||
highlightFeature.setStyle(
|
||||
new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 5,
|
||||
stroke: new Stroke({
|
||||
color: 'black',
|
||||
width: 2,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
vectorLayer.getSource().addFeature(highlightFeature);
|
||||
map.on('pointermove', (e) => {
|
||||
const hit = map.forEachFeatureAtPixel(
|
||||
e.pixel,
|
||||
(feature) => {
|
||||
highlightFeature.setGeometry(feature.getGeometry().clone());
|
||||
return true;
|
||||
},
|
||||
{
|
||||
hitTolerance: 10,
|
||||
}
|
||||
);
|
||||
if (!hit) {
|
||||
highlightFeature.setGeometry(new Point([NaN, NaN]));
|
||||
}
|
||||
});
|
||||
@@ -4,3 +4,13 @@
|
||||
top: auto;
|
||||
right: auto;
|
||||
}
|
||||
.map:-webkit-full-screen {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.map:-ms-fullscreen {
|
||||
height: 100%;
|
||||
}
|
||||
.map:fullscreen {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ title: Advanced Mapbox Vector Tiles
|
||||
shortdesc: Example of a Mapbox vector tiles map with custom tile grid.
|
||||
docs: >
|
||||
A vector tiles map which reuses the same source tiles for subsequent zoom levels to save bandwidth on mobile devices. **Note**: No map will be visible when the access token has expired.
|
||||
tags: "mapbox, vector, tiles, mobile"
|
||||
tags: "mapbox, vector, tiles, mobile, grid"
|
||||
resources:
|
||||
- resources/mapbox-streets-v6-style.js
|
||||
cloak:
|
||||
|
||||
@@ -92,7 +92,7 @@ const map = new Map({
|
||||
],
|
||||
target: 'map',
|
||||
view: new View({
|
||||
resolutions: createXYZ({tileSize: 512}).getResolutions89,
|
||||
resolutions: createXYZ({tileSize: 512}).getResolutions(),
|
||||
center: [0, 0],
|
||||
zoom: 2,
|
||||
}),
|
||||
|
||||
11
examples/ogc-map-tiles-geographic.html
Normal file
11
examples/ogc-map-tiles-geographic.html
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: OGC Map Tiles (Geographic)
|
||||
shortdesc: Rendering map tiles from an OGC API – Tiles service.
|
||||
docs: >
|
||||
The <a href="https://ogcapi.ogc.org/tiles/">OGC API – Tiles</a> specification describes how a service can provide map tiles. Because the specification
|
||||
has not yet been finalized, the <code>OGCMapTile</code> source is not yet part of the stable API.
|
||||
tags: "ogc"
|
||||
experimental: true
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
20
examples/ogc-map-tiles-geographic.js
Normal file
20
examples/ogc-map-tiles-geographic.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import Map from '../src/ol/Map.js';
|
||||
import OGCMapTile from '../src/ol/source/OGCMapTile.js';
|
||||
import TileLayer from '../src/ol/layer/Tile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: new OGCMapTile({
|
||||
url: 'https://maps.ecere.com/ogcapi/collections/blueMarble/map/tiles/WorldCRS84Quad',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
view: new View({
|
||||
projection: 'EPSG:4326',
|
||||
center: [0, 0],
|
||||
zoom: 1,
|
||||
}),
|
||||
});
|
||||
11
examples/ogc-map-tiles.html
Normal file
11
examples/ogc-map-tiles.html
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: OGC Map Tiles
|
||||
shortdesc: Rendering map tiles from an OGC API – Tiles service.
|
||||
docs: >
|
||||
The <a href="https://ogcapi.ogc.org/tiles/">OGC API – Tiles</a> specification describes how a service can provide map tiles. Because the specification
|
||||
has not yet been finalized, the <code>OGCMapTile</code> source is not yet part of the stable API.
|
||||
tags: "ogc"
|
||||
experimental: true
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
19
examples/ogc-map-tiles.js
Normal file
19
examples/ogc-map-tiles.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import Map from '../src/ol/Map.js';
|
||||
import OGCMapTile from '../src/ol/source/OGCMapTile.js';
|
||||
import TileLayer from '../src/ol/layer/Tile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: new OGCMapTile({
|
||||
url: 'https://maps.ecere.com/ogcapi/collections/blueMarble/map/tiles/WebMercatorQuad',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
view: new View({
|
||||
center: [0, 0],
|
||||
zoom: 1,
|
||||
}),
|
||||
});
|
||||
11
examples/ogc-vector-tiles.html
Normal file
11
examples/ogc-vector-tiles.html
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: OGC Vector Tiles
|
||||
shortdesc: Rendering vector tiles from an OGC API – Tiles service.
|
||||
docs: >
|
||||
The <a href="https://ogcapi.ogc.org/tiles/">OGC API – Tiles</a> specification describes how a service can provide vector tiles. Because the specification
|
||||
has not yet been finalized, the <code>OGCVectorTile</code> source is not yet part of the stable API.
|
||||
tags: "ogc, vector"
|
||||
experimental: true
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
21
examples/ogc-vector-tiles.js
Normal file
21
examples/ogc-vector-tiles.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import MVT from '../src/ol/format/MVT.js';
|
||||
import Map from '../src/ol/Map.js';
|
||||
import OGCVectorTile from '../src/ol/source/OGCVectorTile.js';
|
||||
import VectorTileLayer from '../src/ol/layer/VectorTile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new VectorTileLayer({
|
||||
source: new OGCVectorTile({
|
||||
url: 'https://maps.ecere.com/ogcapi/collections/NaturalEarth:cultural:ne_10m_admin_0_countries/tiles/WebMercatorQuad',
|
||||
format: new MVT(),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
view: new View({
|
||||
center: [0, 0],
|
||||
zoom: 1,
|
||||
}),
|
||||
});
|
||||
@@ -8,7 +8,7 @@ docs: >
|
||||
Unlike the <a href="export-pdf.html">Export PDF example</a> the on screen map is only used to set the center and rotation.
|
||||
The extent printed depends on the scale and page size. To print the scale bar and attributions the example uses the
|
||||
<a href="https://html2canvas.hertzen.com/" target="_blank">html2canvas</a> library.
|
||||
tags: "print, printing, scale, scaleline, export, pdf"
|
||||
tags: "print, printing, scale, scaleline, export, pdf, grid"
|
||||
resources:
|
||||
- https://html2canvas.hertzen.com/dist/html2canvas.min.js
|
||||
- https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.3.1/jspdf.umd.min.js
|
||||
|
||||
@@ -5,7 +5,7 @@ import XYZ from '../src/ol/source/XYZ.js';
|
||||
import {Image as ImageLayer, Tile as TileLayer} from '../src/ol/layer.js';
|
||||
|
||||
const minVgi = 0;
|
||||
const maxVgi = 0.25;
|
||||
const maxVgi = 0.5;
|
||||
const bins = 10;
|
||||
|
||||
/**
|
||||
@@ -87,7 +87,7 @@ const raster = new RasterSource({
|
||||
summarize: summarize,
|
||||
},
|
||||
});
|
||||
raster.set('threshold', 0.1);
|
||||
raster.set('threshold', 0.25);
|
||||
|
||||
function createCounts(min, max, num) {
|
||||
const values = new Array(num);
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Raster Reprojection
|
||||
shortdesc: Demonstrates client-side raster reprojection between various projections.
|
||||
docs: >
|
||||
This example shows client-side raster reprojection between various projections.
|
||||
tags: "reprojection, projection, proj4js, osm, wms, wmts, hidpi"
|
||||
tags: "reprojection, projection, proj4js, osm, wms, wmts, hidpi, grid"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
<form class="form-inline">
|
||||
|
||||
@@ -3,7 +3,7 @@ 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>
|
||||
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 the <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"
|
||||
|
||||
@@ -3,7 +3,9 @@ layout: example.html
|
||||
title: Translate Features
|
||||
shortdesc: Example of a translate features interaction.
|
||||
docs: >
|
||||
This example demonstrates how the translate and select interactions can be used together. Zoom in to an area of interest and click to select a feature. Then drag the feature around to move it elsewhere on the map.
|
||||
This example demonstrates how the translate and select interactions can be used together.
|
||||
Zoom in to an area of interest and click to select a feature or hold the <code>Shift</code> key and select multiple features.
|
||||
Then drag the features around to move them elsewhere on the map.
|
||||
tags: "drag, translate, feature, vector, editing"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
|
||||
@@ -5,7 +5,7 @@ shortdesc: Example showing vector tiles in EPSG:4326 (styled using ol-mapbox-sty
|
||||
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"
|
||||
tags: "vector tiles, epsg4326, mapbox style, ol-mapbox-style, maptiler, grid"
|
||||
cloak:
|
||||
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
|
||||
value: Get your own API key at https://www.maptiler.com/cloud/
|
||||
|
||||
13
examples/webgl-sea-level.css
Normal file
13
examples/webgl-sea-level.css
Normal file
@@ -0,0 +1,13 @@
|
||||
#level {
|
||||
display: inline-block;
|
||||
width: 150px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
a.location {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#map {
|
||||
background: #8bd4ff;
|
||||
}
|
||||
37
examples/webgl-sea-level.html
Normal file
37
examples/webgl-sea-level.html
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: Sea Level (with WebGL)
|
||||
shortdesc: Render sea level at different elevations
|
||||
docs: >
|
||||
<p>
|
||||
The <code>style</code> property of a WebGL tile layer accepts a <code>color</code> expression that
|
||||
can be used to modify pixel values before rendering. Here, RGB tiles representing elevation
|
||||
data are loaded and rendered so that values at or below sea level are blue, and values
|
||||
above sea level are transparent. The <code>color</code> expression operates on normalized pixel
|
||||
values ranging from 0 to 1. The <code>band</code> operator is used to select normalized values
|
||||
from a single band.
|
||||
</p><p>
|
||||
After converting the normalized RGB values to elevation, the <code>interpolate</code> expression
|
||||
is used to pick colors to apply at a given elevation. Instead of using constant
|
||||
numeric values as the stops in the colors array, the <code>var</code> operator allows you to
|
||||
use a value that can be modified by your application. When the user drags the
|
||||
sea level slider, the <code>layer.updateStyleVariables()</code> function is called to update
|
||||
the <code>level</code> style variable with the value from the slider.
|
||||
</p>
|
||||
tags: "webgl, math, flood"
|
||||
cloak:
|
||||
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
|
||||
value: Get your own API key at https://www.maptiler.com/cloud/
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
<label>
|
||||
Sea level
|
||||
<input id="level" type="range" min="0" max="100" value="1"/>
|
||||
+<span id="output"></span> m
|
||||
</label>
|
||||
<br>
|
||||
Go to
|
||||
<a class="location" data-center="-122.3267,37.8377" data-zoom="11">San Francisco</a>,
|
||||
<a class="location" data-center="-73.9338,40.6861" data-zoom="11">New York</a>,
|
||||
<a class="location" data-center="72.9481,18.9929" data-zoom="11">Mumbai</a>, or
|
||||
<a class="location" data-center="120.831,31.160" data-zoom="9">Shanghai</a>
|
||||
90
examples/webgl-sea-level.js
Normal file
90
examples/webgl-sea-level.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import Map from '../src/ol/Map.js';
|
||||
import TileLayer from '../src/ol/layer/WebGLTile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
import XYZ from '../src/ol/source/XYZ.js';
|
||||
import {fromLonLat} from '../src/ol/proj.js';
|
||||
|
||||
const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
|
||||
const attributions =
|
||||
'<a href="https://www.maptiler.com/copyright/" target="_blank">© MapTiler</a> ' +
|
||||
'<a href="https://www.openstreetmap.org/copyright" target="_blank">© OpenStreetMap contributors</a>';
|
||||
|
||||
const elevation = new TileLayer({
|
||||
opacity: 0.6,
|
||||
source: new XYZ({
|
||||
url:
|
||||
'https://api.maptiler.com/tiles/terrain-rgb/{z}/{x}/{y}.png?key=' + key,
|
||||
maxZoom: 10,
|
||||
tileSize: 512,
|
||||
crossOrigin: 'anonymous',
|
||||
}),
|
||||
style: {
|
||||
variables: {
|
||||
level: 0,
|
||||
},
|
||||
color: [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
// band math operates on normalized values from 0-1
|
||||
// so we scale by 255 to align with the elevation formula
|
||||
// from https://cloud.maptiler.com/tiles/terrain-rgb/
|
||||
[
|
||||
'+',
|
||||
-10000,
|
||||
[
|
||||
'*',
|
||||
0.1 * 255,
|
||||
[
|
||||
'+',
|
||||
['*', 256 * 256, ['band', 1]],
|
||||
['+', ['*', 256, ['band', 2]], ['band', 3]],
|
||||
],
|
||||
],
|
||||
],
|
||||
// use the `level` style variable as a stop in the color ramp
|
||||
['var', 'level'],
|
||||
[139, 212, 255, 1],
|
||||
['+', 0.01, ['var', 'level']],
|
||||
[139, 212, 255, 0],
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: new XYZ({
|
||||
url: 'https://api.maptiler.com/maps/streets/{z}/{x}/{y}.png?key=' + key,
|
||||
attributions: attributions,
|
||||
crossOrigin: 'anonymous',
|
||||
tileSize: 512,
|
||||
}),
|
||||
}),
|
||||
elevation,
|
||||
],
|
||||
view: new View({
|
||||
center: fromLonLat([-122.3267, 37.8377]),
|
||||
zoom: 11,
|
||||
}),
|
||||
});
|
||||
|
||||
const control = document.getElementById('level');
|
||||
const output = document.getElementById('output');
|
||||
control.addEventListener('input', function () {
|
||||
output.innerText = control.value;
|
||||
elevation.updateStyleVariables({level: parseFloat(control.value)});
|
||||
});
|
||||
output.innerText = control.value;
|
||||
|
||||
const locations = document.getElementsByClassName('location');
|
||||
for (let i = 0, ii = locations.length; i < ii; ++i) {
|
||||
locations[i].addEventListener('click', relocate);
|
||||
}
|
||||
|
||||
function relocate(event) {
|
||||
const data = event.target.dataset;
|
||||
const view = map.getView();
|
||||
view.setCenter(fromLonLat(data.center.split(',').map(Number)));
|
||||
view.setZoom(Number(data.zoom));
|
||||
}
|
||||
7
examples/webgl-shaded-relief.css
Normal file
7
examples/webgl-shaded-relief.css
Normal file
@@ -0,0 +1,7 @@
|
||||
table.controls td {
|
||||
padding: 2px 5px;
|
||||
}
|
||||
table.controls td:nth-child(3) {
|
||||
text-align: right;
|
||||
min-width: 3em;
|
||||
}
|
||||
32
examples/webgl-shaded-relief.html
Normal file
32
examples/webgl-shaded-relief.html
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: Shaded Relief (with WebGL)
|
||||
shortdesc: Calculate shaded relief from elevation data
|
||||
docs: >
|
||||
<p>
|
||||
For the shaded relief, a single tiled source of elevation data is used as input.
|
||||
The shaded relief is calculated by the layer's <code>style</code> with a <code>color</code>
|
||||
expression. The style variables are updated when the user drags one of the sliders. The
|
||||
<code>band</code> operator is used to sample data from neighboring pixels for calculating slope and
|
||||
aspect, which is done with the <code>['band', bandIndex, xOffset, yOffset]</code> syntax.
|
||||
</p>
|
||||
tags: "webgl, shaded relief"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
<table class="controls">
|
||||
<tr>
|
||||
<td><label for="vert">vertical exaggeration:</label></td>
|
||||
<td><input id="vert" type="range" min="1" max="5" value="1"/></td>
|
||||
<td><span id="vertOut"></span> x</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="sunEl">sun elevation:</label></td>
|
||||
<td><input id="sunEl" type="range" min="0" max="90" value="45"/></td>
|
||||
<td><span id="sunElOut"></span> °</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="sunAz">sun azimuth:</label></td>
|
||||
<td><input id="sunAz" type="range" min="0" max="360" value="45"/></td>
|
||||
<td><span id="sunAzOut"></span> °</td>
|
||||
</tr>
|
||||
</table>
|
||||
91
examples/webgl-shaded-relief.js
Normal file
91
examples/webgl-shaded-relief.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import Map from '../src/ol/Map.js';
|
||||
import View from '../src/ol/View.js';
|
||||
import {OSM, XYZ} from '../src/ol/source.js';
|
||||
import {WebGLTile as TileLayer} from '../src/ol/layer.js';
|
||||
|
||||
const variables = {};
|
||||
|
||||
// The method used to extract elevations from the DEM.
|
||||
// In this case the format used is
|
||||
// red + green * 2 + blue * 3
|
||||
//
|
||||
// Other frequently used methods include the Mapbox format
|
||||
// (red * 256 * 256 + green * 256 + blue) * 0.1 - 10000
|
||||
// and the Terrarium format
|
||||
// (red * 256 + green + blue / 256) - 32768
|
||||
function elevation(xOffset, yOffset) {
|
||||
return [
|
||||
'+',
|
||||
['*', 256, ['band', 1, xOffset, yOffset]],
|
||||
[
|
||||
'+',
|
||||
['*', 2 * 256, ['band', 2, xOffset, yOffset]],
|
||||
['*', 3 * 256, ['band', 3, xOffset, yOffset]],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// Generates a shaded relief image given elevation data. Uses a 3x3
|
||||
// neighborhood for determining slope and aspect.
|
||||
const dp = ['*', 2, ['resolution']];
|
||||
const z0x = ['*', ['var', 'vert'], elevation(-1, 0)];
|
||||
const z1x = ['*', ['var', 'vert'], elevation(1, 0)];
|
||||
const dzdx = ['/', ['-', z1x, z0x], dp];
|
||||
const z0y = ['*', ['var', 'vert'], elevation(0, -1)];
|
||||
const z1y = ['*', ['var', 'vert'], elevation(0, 1)];
|
||||
const dzdy = ['/', ['-', z1y, z0y], dp];
|
||||
const slope = ['atan', ['^', ['+', ['^', dzdx, 2], ['^', dzdy, 2]], 0.5]];
|
||||
const aspect = ['clamp', ['atan', ['-', 0, dzdx], dzdy], -Math.PI, Math.PI];
|
||||
const sunEl = ['*', Math.PI / 180, ['var', 'sunEl']];
|
||||
const sunAz = ['*', Math.PI / 180, ['var', 'sunAz']];
|
||||
|
||||
const cosIncidence = [
|
||||
'+',
|
||||
['*', ['sin', sunEl], ['cos', slope]],
|
||||
['*', ['*', ['cos', sunEl], ['sin', slope]], ['cos', ['-', sunAz, aspect]]],
|
||||
];
|
||||
const scaled = ['*', 255, cosIncidence];
|
||||
|
||||
const shadedRelief = new TileLayer({
|
||||
opacity: 0.3,
|
||||
source: new XYZ({
|
||||
url: 'https://{a-d}.tiles.mapbox.com/v3/aj.sf-dem/{z}/{x}/{y}.png',
|
||||
crossOrigin: 'anonymous',
|
||||
}),
|
||||
style: {
|
||||
variables: variables,
|
||||
color: ['color', scaled, scaled, scaled],
|
||||
},
|
||||
});
|
||||
|
||||
const controlIds = ['vert', 'sunEl', 'sunAz'];
|
||||
controlIds.forEach(function (id) {
|
||||
const control = document.getElementById(id);
|
||||
const output = document.getElementById(id + 'Out');
|
||||
function updateValues() {
|
||||
output.innerText = control.value;
|
||||
variables[id] = Number(control.value);
|
||||
}
|
||||
updateValues();
|
||||
control.addEventListener('input', () => {
|
||||
updateValues();
|
||||
shadedRelief.updateStyleVariables(variables);
|
||||
});
|
||||
});
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: new OSM(),
|
||||
}),
|
||||
shadedRelief,
|
||||
],
|
||||
view: new View({
|
||||
extent: [-13675026, 4439648, -13580856, 4580292],
|
||||
center: [-13615645, 4497969],
|
||||
minZoom: 10,
|
||||
maxZoom: 16,
|
||||
zoom: 13,
|
||||
}),
|
||||
});
|
||||
4
examples/webgl-tile-style.css
Normal file
4
examples/webgl-tile-style.css
Normal file
@@ -0,0 +1,4 @@
|
||||
#controls {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
31
examples/webgl-tile-style.html
Normal file
31
examples/webgl-tile-style.html
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: WebGL Tile Layer Styles
|
||||
shortdesc: Styling raster tiles with WebGL.
|
||||
docs: >
|
||||
The `style` property of a WebGL tile layer can be used to adjust properties like
|
||||
`exposure`, `contrast`, and `saturation`. Typically those values would be set to
|
||||
numeric constants to apply a filter to imagery. In this example, the style properties
|
||||
are set to variables that can be updated based on application state. Adjusting the
|
||||
sliders results in a call to `layer.updateStyleVariables()` to use new values for the
|
||||
associated style properties.
|
||||
tags: "webgl, style"
|
||||
cloak:
|
||||
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
|
||||
value: Get your own API key at https://www.maptiler.com/cloud/
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
<div id="controls">
|
||||
<label>
|
||||
<input id="exposure" type="range" min="-0.5" max="0.5" step="0.01">
|
||||
<br>exposure <span id="exposure-value"></span>
|
||||
</label>
|
||||
<label>
|
||||
<input id="contrast" type="range" min="-0.5" max="0.5" step="0.01">
|
||||
<br>contrast <span id="contrast-value"></span>
|
||||
</label>
|
||||
<label>
|
||||
<input id="saturation" type="range" min="-0.5" max="0.5" step="0.01">
|
||||
<br>saturation <span id="saturation-value"></span>
|
||||
</label>
|
||||
</div>
|
||||
55
examples/webgl-tile-style.js
Normal file
55
examples/webgl-tile-style.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import Map from '../src/ol/Map.js';
|
||||
import TileLayer from '../src/ol/layer/WebGLTile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
import XYZ from '../src/ol/source/XYZ.js';
|
||||
|
||||
const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
|
||||
const attributions =
|
||||
'<a href="https://www.maptiler.com/copyright/" target="_blank">© MapTiler</a> ' +
|
||||
'<a href="https://www.openstreetmap.org/copyright" target="_blank">© OpenStreetMap contributors</a>';
|
||||
|
||||
const variables = {
|
||||
exposure: 0,
|
||||
contrast: 0,
|
||||
saturation: 0,
|
||||
};
|
||||
|
||||
const layer = new TileLayer({
|
||||
style: {
|
||||
exposure: ['var', 'exposure'],
|
||||
contrast: ['var', 'contrast'],
|
||||
saturation: ['var', 'saturation'],
|
||||
variables: variables,
|
||||
},
|
||||
source: new XYZ({
|
||||
crossOrigin: 'anonymous', // TODO: determine if we can avoid this
|
||||
attributions: attributions,
|
||||
url: 'https://api.maptiler.com/tiles/satellite/{z}/{x}/{y}.jpg?key=' + key,
|
||||
maxZoom: 20,
|
||||
}),
|
||||
});
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [layer],
|
||||
view: new View({
|
||||
center: [0, 0],
|
||||
zoom: 0,
|
||||
}),
|
||||
});
|
||||
|
||||
for (const name in variables) {
|
||||
const element = document.getElementById(name);
|
||||
const value = variables[name];
|
||||
element.value = value.toString();
|
||||
document.getElementById(`${name}-value`).innerText = `(${value})`;
|
||||
|
||||
element.addEventListener('input', function (event) {
|
||||
const value = parseFloat(event.target.value);
|
||||
document.getElementById(`${name}-value`).innerText = `(${value})`;
|
||||
|
||||
const updates = {};
|
||||
updates[name] = value;
|
||||
layer.updateStyleVariables(updates);
|
||||
});
|
||||
}
|
||||
9
examples/webgl-tiles.html
Normal file
9
examples/webgl-tiles.html
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: WebGL Tiles
|
||||
shortdesc: Rendering raster data with WebGL.
|
||||
docs: >
|
||||
This example uses WebGL to raster tiles on a map.
|
||||
tags: "webgl, osm"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
17
examples/webgl-tiles.js
Normal file
17
examples/webgl-tiles.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import Map from '../src/ol/Map.js';
|
||||
import OSM from '../src/ol/source/OSM.js';
|
||||
import TileLayer from '../src/ol/layer/WebGLTile.js';
|
||||
import View from '../src/ol/View.js';
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: new OSM(),
|
||||
}),
|
||||
],
|
||||
view: new View({
|
||||
center: [0, 0],
|
||||
zoom: 0,
|
||||
}),
|
||||
});
|
||||
@@ -101,6 +101,8 @@ export default {
|
||||
resolve: {
|
||||
fallback: {
|
||||
fs: false,
|
||||
http: false,
|
||||
https: false,
|
||||
},
|
||||
alias: {
|
||||
// allow imports from 'ol/module' instead of specifiying the source path
|
||||
@@ -9,7 +9,12 @@ module.exports = function loader() {
|
||||
build(this.resource, {minify})
|
||||
.then((chunk) => {
|
||||
for (const filePath in chunk.modules) {
|
||||
this.addDependency(filePath);
|
||||
try {
|
||||
const dependency = require.resolve(filePath);
|
||||
this.addDependency(dependency);
|
||||
} catch (e) {
|
||||
// empty catch block
|
||||
}
|
||||
}
|
||||
callback(null, chunk.code);
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: WMS 512x256 Tiles
|
||||
shortdesc: Example of a WMS layer with 512x256px tiles.
|
||||
shortdesc: Example of a WMS layer with a custom grid with 512x256px tiles.
|
||||
docs: >
|
||||
WMS can serve arbitrary tile sizes. This example uses a custom tile grid with non-square tiles.
|
||||
tags: "wms, tile, non-square"
|
||||
tags: "wms, tile, non-square, grid"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
|
||||
@@ -4,7 +4,7 @@ title: WMTS Tile Transitions
|
||||
shortdesc: Example of smooth tile transitions when changing the dimension of a WMTS layer.
|
||||
docs: >
|
||||
Demonstrates smooth reloading of layers when changing a dimension continuously. The demonstration layer is a global sea-level computation (flooding computation from <a href="https://scalgo.com/">SCALGO</a>, underlying data from <a href="https://cgiarcsi.community/data/srtm-90m-digital-elevation-database-v4-1">CGIAR-CSI SRTM</a>) where cells that are flooded if the sea-level rises to more than <em>x</em> m are colored blue. The user selects the sea-level dimension using a slider.
|
||||
tags: "wmts, parameter, transition"
|
||||
tags: "wmts, parameter, transition, grid"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
<label>
|
||||
|
||||
@@ -9,6 +9,6 @@ docs: >
|
||||
and
|
||||
[Documentation de l’offre de données et services de l’IGN](https://geoservices.ign.fr/documentation/diffusion/documentation-offre.html)
|
||||
(french).
|
||||
tags: "french, ign, geoportail, wmts"
|
||||
tags: "french, ign, geoportail, wmts, grid"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
|
||||
@@ -4,6 +4,6 @@ title: WMTS
|
||||
shortdesc: Example of a WMTS source.
|
||||
docs: >
|
||||
This example shows how to manually create the configuration for accessing a WMTS. The [WMTS Layer from capabilities example](wmts-layer-from-capabilities.html) shows how to create the configuration from a GetCapabilities response.
|
||||
tags: "wmts"
|
||||
tags: "wmts, grid"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
|
||||
2095
package-lock.json
generated
2095
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ol",
|
||||
"version": "6.6.1",
|
||||
"version": "6.7.0",
|
||||
"description": "OpenLayers mapping library",
|
||||
"keywords": [
|
||||
"map",
|
||||
@@ -18,12 +18,12 @@
|
||||
"test": "npm run test-browser && npm run test-node && npm run test-rendering -- --force",
|
||||
"karma": "karma start test/browser/karma.config.cjs",
|
||||
"start": "npm run serve-examples",
|
||||
"serve-examples": "webpack serve --config examples/webpack/config.js --mode development",
|
||||
"build-examples": "webpack --config examples/webpack/config.js --mode production",
|
||||
"serve-examples": "webpack serve --config examples/webpack/config.mjs --mode development",
|
||||
"build-examples": "webpack --config examples/webpack/config.mjs --mode production",
|
||||
"build-package": "npm run transpile && npm run copy-css && npm run generate-types && node tasks/prepare-package.js",
|
||||
"build-index": "shx rm -f build/index.js && npm run build-package && node tasks/generate-index.js",
|
||||
"build-legacy": "shx rm -rf build/legacy && npm run build-index && webpack --config config/webpack-config-legacy-build.js && cleancss --source-map src/ol/ol.css -o build/legacy/ol.css",
|
||||
"build-site": "npm run build-examples && npm run apidoc && mkdir -p build/site && cp site/index.html build/site && mv build/apidoc build/site/apidoc && mv build/examples build/site/examples",
|
||||
"build-legacy": "shx rm -rf build/legacy && npm run build-index && webpack --config config/webpack-config-legacy-build.mjs && cleancss --source-map src/ol/ol.css -o build/legacy/ol.css",
|
||||
"build-site": "shx rm -rf build/site && npm run build-examples && npm run apidoc && shx mkdir -p build/site && shx cp site/index.html build/site/ && shx mv build/apidoc build/examples build/site/",
|
||||
"copy-css": "shx cp src/ol/ol.css build/ol/ol.css",
|
||||
"generate-types": "npx --package=typescript@3.8.3 -y -- tsc --project config/tsconfig-build.json --declaration --declarationMap --emitDeclarationOnly --outdir build/ol",
|
||||
"transpile": "shx rm -rf build/ol && shx mkdir -p build/ol && shx cp -rf src/ol build/ol/src && node tasks/serialize-workers.cjs && npx --package=typescript@4.3.5 -y -- tsc --project config/tsconfig-build.json",
|
||||
@@ -45,6 +45,7 @@
|
||||
"url": "https://opencollective.com/openlayers"
|
||||
},
|
||||
"dependencies": {
|
||||
"geotiff": "^1.0.5",
|
||||
"ol-mapbox-style": "^6.4.1",
|
||||
"pbf": "3.2.1",
|
||||
"rbush": "^3.0.1"
|
||||
@@ -55,7 +56,7 @@
|
||||
"@babel/preset-env": "^7.4.4",
|
||||
"@openlayers/eslint-plugin": "^4.0.0",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-commonjs": "^19.0.0",
|
||||
"@rollup/plugin-commonjs": "^20.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"@types/arcgis-rest-api": "^10.4.4",
|
||||
"@types/geojson": "^7946.0.7",
|
||||
@@ -65,7 +66,7 @@
|
||||
"buble": "^0.20.0",
|
||||
"buble-loader": "^0.5.1",
|
||||
"chaikin-smooth": "^1.0.4",
|
||||
"clean-css-cli": "5.3.0",
|
||||
"clean-css-cli": "5.3.3",
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"coverage-istanbul-loader": "^3.0.5",
|
||||
"coveralls": "3.1.1",
|
||||
@@ -77,7 +78,7 @@
|
||||
"front-matter": "^4.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"glob": "^7.1.5",
|
||||
"globby": "^11.0.0",
|
||||
"globby": "^12.0.0",
|
||||
"handlebars": "4.7.7",
|
||||
"istanbul": "0.4.5",
|
||||
"jquery": "3.6.0",
|
||||
@@ -92,12 +93,13 @@
|
||||
"karma-sourcemap-loader": "^0.3.8",
|
||||
"karma-webpack": "^5.0.0",
|
||||
"loglevelnext": "^5.0.5",
|
||||
"marked": "2.1.3",
|
||||
"mocha": "9.0.2",
|
||||
"marked": "3.0.2",
|
||||
"mocha": "9.1.1",
|
||||
"pixelmatch": "^5.1.0",
|
||||
"pngjs": "^6.0.0",
|
||||
"proj4": "2.7.4",
|
||||
"puppeteer": "10.1.0",
|
||||
"proj4": "^2.7.5",
|
||||
"puppeteer": "10.2.0",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"rollup": "^2.42.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"serve-static": "^1.14.0",
|
||||
@@ -110,7 +112,7 @@
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack-dev-middleware": "^5.0.0",
|
||||
"webpack-dev-server": "^4.0.0-beta.2",
|
||||
"webpack-sources": "^2.2.0",
|
||||
"webpack-sources": "^3.2.0",
|
||||
"worker-loader": "^3.0.8",
|
||||
"yargs": "^17.0.0"
|
||||
},
|
||||
|
||||
76
src/ol/DataTile.js
Normal file
76
src/ol/DataTile.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @module ol/DataTile
|
||||
*/
|
||||
import Tile from './Tile.js';
|
||||
import TileState from './TileState.js';
|
||||
|
||||
/**
|
||||
* Data that can be used with a DataTile.
|
||||
* @typedef {Uint8Array|Uint8ClampedArray|DataView} Data
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Options
|
||||
* @property {import("./tilecoord.js").TileCoord} tileCoord Tile coordinate.
|
||||
* @property {function() : Promise<Data>} loader Data loader.
|
||||
* @property {number} [transition=250] A duration for tile opacity
|
||||
* transitions in milliseconds. A duration of 0 disables the opacity transition.
|
||||
* @api
|
||||
*/
|
||||
|
||||
class DataTile extends Tile {
|
||||
/**
|
||||
* @param {Options} options Tile options.
|
||||
*/
|
||||
constructor(options) {
|
||||
const state = TileState.IDLE;
|
||||
|
||||
super(options.tileCoord, state, {transition: options.transition});
|
||||
|
||||
this.loader_ = options.loader;
|
||||
this.data_ = null;
|
||||
this.error_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data for the tile.
|
||||
* @return {Data} Tile data.
|
||||
* @api
|
||||
*/
|
||||
getData() {
|
||||
return this.data_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any loading error.
|
||||
* @return {Error} Loading error.
|
||||
* @api
|
||||
*/
|
||||
getError() {
|
||||
return this.error_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load not yet loaded URI.
|
||||
* @api
|
||||
*/
|
||||
load() {
|
||||
this.state = TileState.LOADING;
|
||||
this.changed();
|
||||
|
||||
const self = this;
|
||||
this.loader_()
|
||||
.then(function (data) {
|
||||
self.data_ = data;
|
||||
self.state = TileState.LOADED;
|
||||
self.changed();
|
||||
})
|
||||
.catch(function (error) {
|
||||
self.error_ = error;
|
||||
self.state = TileState.ERROR;
|
||||
self.changed();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default DataTile;
|
||||
@@ -11,7 +11,7 @@ import {listen, unlistenByKey} from './events.js';
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Feature|import("./render/Feature.js").default} FeatureLike
|
||||
* @typedef {Feature<import("./geom/Geometry.js").default>|import("./render/Feature.js").default} FeatureLike
|
||||
*/
|
||||
|
||||
/***
|
||||
@@ -22,6 +22,11 @@ import {listen, unlistenByKey} from './events.js';
|
||||
* |'change:geometry', Return>} FeatureOnSignature
|
||||
*/
|
||||
|
||||
/***
|
||||
* @template Geometry
|
||||
* @typedef {Object<string, *> & { geometry?: Geometry }} ObjectWithGeometry
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* A vector object for geographic features with a geometry and other
|
||||
@@ -69,7 +74,7 @@ import {listen, unlistenByKey} from './events.js';
|
||||
*/
|
||||
class Feature extends BaseObject {
|
||||
/**
|
||||
* @param {Geometry|Object<string, *>} [opt_geometryOrProperties]
|
||||
* @param {Geometry|ObjectWithGeometry<Geometry>} [opt_geometryOrProperties]
|
||||
* You may pass a Geometry object directly, or an object literal containing
|
||||
* properties. If you pass an object literal, you may include a Geometry
|
||||
* associated with a `geometry` key.
|
||||
@@ -144,17 +149,17 @@ class Feature extends BaseObject {
|
||||
/**
|
||||
* Clone this feature. If the original feature has a geometry it
|
||||
* is also cloned. The feature id is not set in the clone.
|
||||
* @return {Feature} The clone.
|
||||
* @return {Feature<Geometry>} The clone.
|
||||
* @api
|
||||
*/
|
||||
clone() {
|
||||
const clone = new Feature(
|
||||
this.hasProperties() ? this.getProperties() : null
|
||||
const clone = /** @type {Feature<Geometry>} */ (
|
||||
new Feature(this.hasProperties() ? this.getProperties() : null)
|
||||
);
|
||||
clone.setGeometryName(this.getGeometryName());
|
||||
const geometry = this.getGeometry();
|
||||
if (geometry) {
|
||||
clone.setGeometry(geometry.clone());
|
||||
clone.setGeometry(/** @type {Geometry} */ (geometry.clone()));
|
||||
}
|
||||
const style = this.getStyle();
|
||||
if (style) {
|
||||
|
||||
@@ -66,7 +66,7 @@ import {removeNode} from './dom.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} AtPixelOptions
|
||||
* @property {undefined|function(import("./layer/Layer.js").default): boolean} [layerFilter] Layer filter
|
||||
* @property {undefined|function(import("./layer/Layer.js").default<import("./source/Source").default>): boolean} [layerFilter] Layer filter
|
||||
* function. The filter function will receive one argument, the
|
||||
* {@link module:ol/layer/Layer layer-candidate} and it should return a boolean value.
|
||||
* Only layers which are visible and for which this function returns `true`
|
||||
@@ -143,6 +143,11 @@ import {removeNode} from './dom.js';
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fires import("./MapBrowserEvent.js").MapBrowserEvent
|
||||
* @fires import("./MapEvent.js").MapEvent
|
||||
* @fires import("./render/Event.js").default#precompose
|
||||
* @fires import("./render/Event.js").default#postcompose
|
||||
* @fires import("./render/Event.js").default#rendercomplete
|
||||
* @api
|
||||
*/
|
||||
class PluggableMap extends BaseObject {
|
||||
@@ -383,16 +388,6 @@ class PluggableMap extends BaseObject {
|
||||
// is "defined" already.
|
||||
this.setProperties(optionsInternal.values);
|
||||
|
||||
this.controls.forEach(
|
||||
/**
|
||||
* @param {import("./control/Control.js").default} control Control.
|
||||
* @this {PluggableMap}
|
||||
*/
|
||||
function (control) {
|
||||
control.setMap(this);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.controls.addEventListener(
|
||||
CollectionEventType.ADD,
|
||||
/**
|
||||
@@ -413,16 +408,6 @@ class PluggableMap extends BaseObject {
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.interactions.forEach(
|
||||
/**
|
||||
* @param {import("./interaction/Interaction.js").default} interaction Interaction.
|
||||
* @this {PluggableMap}
|
||||
*/
|
||||
function (interaction) {
|
||||
interaction.setMap(this);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.interactions.addEventListener(
|
||||
CollectionEventType.ADD,
|
||||
/**
|
||||
@@ -443,8 +428,6 @@ class PluggableMap extends BaseObject {
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.overlays_.forEach(this.addOverlayInternal_.bind(this));
|
||||
|
||||
this.overlays_.addEventListener(
|
||||
CollectionEventType.ADD,
|
||||
/**
|
||||
@@ -473,6 +456,28 @@ class PluggableMap extends BaseObject {
|
||||
event.element.setMap(null);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.controls.forEach(
|
||||
/**
|
||||
* @param {import("./control/Control.js").default} control Control.
|
||||
* @this {PluggableMap}
|
||||
*/
|
||||
function (control) {
|
||||
control.setMap(this);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.interactions.forEach(
|
||||
/**
|
||||
* @param {import("./interaction/Interaction.js").default} interaction Interaction.
|
||||
* @this {PluggableMap}
|
||||
*/
|
||||
function (interaction) {
|
||||
interaction.setMap(this);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.overlays_.forEach(this.addOverlayInternal_.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -553,7 +558,7 @@ class PluggableMap extends BaseObject {
|
||||
* callback with each intersecting feature. Layers included in the detection can
|
||||
* be configured through the `layerFilter` option in `opt_options`.
|
||||
* @param {import("./pixel.js").Pixel} pixel Pixel.
|
||||
* @param {function(import("./Feature.js").FeatureLike, import("./layer/Layer.js").default, import("./geom/SimpleGeometry.js").default): T} callback Feature callback. The callback will be
|
||||
* @param {function(import("./Feature.js").FeatureLike, import("./layer/Layer.js").default<import("./source/Source").default>, import("./geom/SimpleGeometry.js").default): T} callback Feature callback. The callback will be
|
||||
* called with two arguments. The first argument is one
|
||||
* {@link module:ol/Feature feature} or
|
||||
* {@link module:ol/render/Feature render feature} at the pixel, the second is
|
||||
@@ -563,7 +568,7 @@ class PluggableMap extends BaseObject {
|
||||
* @param {AtPixelOptions} [opt_options] Optional options.
|
||||
* @return {T|undefined} Callback result, i.e. the return value of last
|
||||
* callback execution, or the first truthy callback return value.
|
||||
* @template S,T
|
||||
* @template T
|
||||
* @api
|
||||
*/
|
||||
forEachFeatureAtPixel(pixel, callback, opt_options) {
|
||||
@@ -1524,7 +1529,14 @@ class PluggableMap extends BaseObject {
|
||||
parseFloat(computedStyle['borderBottomWidth']);
|
||||
if (!isNaN(width) && !isNaN(height)) {
|
||||
size = [width, height];
|
||||
if (!hasArea(size)) {
|
||||
if (
|
||||
!hasArea(size) &&
|
||||
!!(
|
||||
targetElement.offsetWidth ||
|
||||
targetElement.offsetHeight ||
|
||||
targetElement.getClientRects().length
|
||||
)
|
||||
) {
|
||||
// eslint-disable-next-line
|
||||
console.warn(
|
||||
"No map visible because the map container's width or height are 0."
|
||||
|
||||
@@ -74,7 +74,7 @@ export function includes(arr, obj) {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Array<number>} arr Array in desccending order.
|
||||
* @param {Array<number>} arr Array in descending order.
|
||||
* @param {number} target Target.
|
||||
* @param {number|NearestDirectionFunction} direction
|
||||
* 0 means return the nearest,
|
||||
@@ -206,7 +206,7 @@ export function equals(arr1, arr2) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the passed array such that the relative order of equal elements is preverved.
|
||||
* Sort the passed array such that the relative order of equal elements is preserved.
|
||||
* See https://en.wikipedia.org/wiki/Sorting_algorithm#Stability for details.
|
||||
* @param {Array<*>} arr The array to sort (modifies original).
|
||||
* @param {!function(*, *): number} compareFnc Comparison function.
|
||||
|
||||
@@ -104,8 +104,8 @@ class MousePosition extends Control {
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* Change this to `false` when removing the deprecated `undefinedHTML` option.
|
||||
* @type {boolean}
|
||||
*/
|
||||
let renderOnMouseOut = true;
|
||||
|
||||
|
||||
@@ -19,12 +19,16 @@ export function createCanvasContext2D(
|
||||
opt_canvasPool,
|
||||
opt_Context2DSettings
|
||||
) {
|
||||
const canvas =
|
||||
opt_canvasPool && opt_canvasPool.length
|
||||
? opt_canvasPool.shift()
|
||||
: WORKER_OFFSCREEN_CANVAS
|
||||
? new OffscreenCanvas(opt_width || 300, opt_height || 300)
|
||||
: document.createElement('canvas');
|
||||
/** @type {HTMLCanvasElement|OffscreenCanvas} */
|
||||
let canvas;
|
||||
if (opt_canvasPool && opt_canvasPool.length) {
|
||||
canvas = opt_canvasPool.shift();
|
||||
} else if (WORKER_OFFSCREEN_CANVAS) {
|
||||
canvas = new OffscreenCanvas(opt_width || 300, opt_height || 300);
|
||||
} else {
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.style.all = 'initial';
|
||||
}
|
||||
if (opt_width) {
|
||||
canvas.width = opt_width;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export {default as OWS} from './format/OWS.js';
|
||||
export {default as Polyline} from './format/Polyline.js';
|
||||
export {default as TopoJSON} from './format/TopoJSON.js';
|
||||
export {default as WFS} from './format/WFS.js';
|
||||
export {default as WKB} from './format/WKB.js';
|
||||
export {default as WKT} from './format/WKT.js';
|
||||
export {default as WMSCapabilities} from './format/WMSCapabilities.js';
|
||||
export {default as WMSGetFeatureInfo} from './format/WMSGetFeatureInfo.js';
|
||||
|
||||
@@ -76,6 +76,12 @@ class FeatureFormat {
|
||||
* @type {import("../proj/Projection.js").default|undefined}
|
||||
*/
|
||||
this.defaultFeatureProjection = undefined;
|
||||
|
||||
/**
|
||||
* A list media types supported by the format in descending order of preference.
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.supportedMediaTypes = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -132,6 +132,8 @@ class GMLBase extends XMLFeature {
|
||||
'featureMember': makeArrayPusher(this.readFeaturesInternal),
|
||||
'featureMembers': makeReplacer(this.readFeaturesInternal),
|
||||
};
|
||||
|
||||
this.supportedMediaTypes = ['application/gml+xml'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,7 @@ import {includes} from '../array.js';
|
||||
import {
|
||||
readDateTime,
|
||||
readDecimal,
|
||||
readNonNegativeInteger,
|
||||
readPositiveInteger,
|
||||
readString,
|
||||
writeDateTimeTextNode,
|
||||
writeDecimalTextNode,
|
||||
@@ -262,7 +262,7 @@ const RTE_PARSERS = makeStructureNS(NAMESPACE_URIS, {
|
||||
'desc': makeObjectPropertySetter(readString),
|
||||
'src': makeObjectPropertySetter(readString),
|
||||
'link': parseLink,
|
||||
'number': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'number': makeObjectPropertySetter(readPositiveInteger),
|
||||
'extensions': parseExtensions,
|
||||
'type': makeObjectPropertySetter(readString),
|
||||
'rtept': parseRtePt,
|
||||
@@ -289,7 +289,7 @@ const TRK_PARSERS = makeStructureNS(NAMESPACE_URIS, {
|
||||
'desc': makeObjectPropertySetter(readString),
|
||||
'src': makeObjectPropertySetter(readString),
|
||||
'link': parseLink,
|
||||
'number': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'number': makeObjectPropertySetter(readPositiveInteger),
|
||||
'type': makeObjectPropertySetter(readString),
|
||||
'extensions': parseExtensions,
|
||||
'trkseg': parseTrkSeg,
|
||||
@@ -332,12 +332,12 @@ const WPT_PARSERS = makeStructureNS(NAMESPACE_URIS, {
|
||||
'sym': makeObjectPropertySetter(readString),
|
||||
'type': makeObjectPropertySetter(readString),
|
||||
'fix': makeObjectPropertySetter(readString),
|
||||
'sat': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'sat': makeObjectPropertySetter(readPositiveInteger),
|
||||
'hdop': makeObjectPropertySetter(readDecimal),
|
||||
'vdop': makeObjectPropertySetter(readDecimal),
|
||||
'pdop': makeObjectPropertySetter(readDecimal),
|
||||
'ageofdgpsdata': makeObjectPropertySetter(readDecimal),
|
||||
'dgpsid': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'dgpsid': makeObjectPropertySetter(readPositiveInteger),
|
||||
'extensions': parseExtensions,
|
||||
});
|
||||
|
||||
|
||||
@@ -82,6 +82,11 @@ class GeoJSON extends JSONFeature {
|
||||
* @private
|
||||
*/
|
||||
this.extractGeometryName_ = options.extractGeometryName;
|
||||
|
||||
this.supportedMediaTypes = [
|
||||
'application/geo+json',
|
||||
'application/vnd.geo+json',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ import GeometryType from '../geom/GeometryType.js';
|
||||
import Icon from '../style/Icon.js';
|
||||
import IconAnchorUnits from '../style/IconAnchorUnits.js';
|
||||
import IconOrigin from '../style/IconOrigin.js';
|
||||
import ImageState from '../ImageState.js';
|
||||
import LineString from '../geom/LineString.js';
|
||||
import MultiLineString from '../geom/MultiLineString.js';
|
||||
import MultiPoint from '../geom/MultiPoint.js';
|
||||
@@ -231,11 +232,6 @@ let DEFAULT_IMAGE_STYLE_SIZE;
|
||||
*/
|
||||
let DEFAULT_IMAGE_STYLE_SRC;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
let DEFAULT_IMAGE_SCALE_MULTIPLIER;
|
||||
|
||||
/**
|
||||
* @type {import("../style/Image.js").default}
|
||||
*/
|
||||
@@ -311,6 +307,15 @@ export function getDefaultStyleArray() {
|
||||
return DEFAULT_STYLE_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that returns the scale needed to normalize an icon image to 32 pixels.
|
||||
* @param {import("../size.js").Size} size Image size.
|
||||
* @return {number} Scale.
|
||||
*/
|
||||
function scaleForSize(size) {
|
||||
return 32 / Math.min(size[0], size[1]);
|
||||
}
|
||||
|
||||
function createStyleDefaults() {
|
||||
DEFAULT_COLOR = [255, 255, 255, 1];
|
||||
|
||||
@@ -318,7 +323,7 @@ function createStyleDefaults() {
|
||||
color: DEFAULT_COLOR,
|
||||
});
|
||||
|
||||
DEFAULT_IMAGE_STYLE_ANCHOR = [20, 2]; // FIXME maybe [8, 32] ?
|
||||
DEFAULT_IMAGE_STYLE_ANCHOR = [20, 2];
|
||||
|
||||
DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS = IconAnchorUnits.PIXELS;
|
||||
|
||||
@@ -329,8 +334,6 @@ function createStyleDefaults() {
|
||||
DEFAULT_IMAGE_STYLE_SRC =
|
||||
'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png';
|
||||
|
||||
DEFAULT_IMAGE_SCALE_MULTIPLIER = 0.5;
|
||||
|
||||
DEFAULT_IMAGE_STYLE = new Icon({
|
||||
anchor: DEFAULT_IMAGE_STYLE_ANCHOR,
|
||||
anchorOrigin: IconOrigin.BOTTOM_LEFT,
|
||||
@@ -338,7 +341,7 @@ function createStyleDefaults() {
|
||||
anchorYUnits: DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS,
|
||||
crossOrigin: 'anonymous',
|
||||
rotation: 0,
|
||||
scale: DEFAULT_IMAGE_SCALE_MULTIPLIER,
|
||||
scale: scaleForSize(DEFAULT_IMAGE_STYLE_SIZE),
|
||||
size: DEFAULT_IMAGE_STYLE_SIZE,
|
||||
src: DEFAULT_IMAGE_STYLE_SRC,
|
||||
});
|
||||
@@ -489,6 +492,8 @@ class KML extends XMLFeature {
|
||||
this.iconUrlFunction_ = options.iconUrlFunction
|
||||
? options.iconUrlFunction
|
||||
: defaultIconUrlFunction;
|
||||
|
||||
this.supportedMediaTypes = ['application/vnd.google-earth.kml+xml'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -939,16 +944,14 @@ function createNameStyleFunction(foundStyle, name) {
|
||||
let textAlign = 'start';
|
||||
const imageStyle = foundStyle.getImage();
|
||||
if (imageStyle) {
|
||||
let imageSize = imageStyle.getImageSize();
|
||||
if (imageSize === null) {
|
||||
imageSize = DEFAULT_IMAGE_STYLE_SIZE;
|
||||
}
|
||||
if (imageSize.length == 2) {
|
||||
const imageSize = imageStyle.getSize();
|
||||
if (imageSize && imageSize.length == 2) {
|
||||
const imageScale = imageStyle.getScaleArray();
|
||||
const anchor = imageStyle.getAnchor();
|
||||
// Offset the label to be centered to the right of the icon,
|
||||
// if there is one.
|
||||
textOffset[0] = (imageScale[0] * imageSize[0]) / 2;
|
||||
textOffset[1] = (-imageScale[1] * imageSize[1]) / 2;
|
||||
textOffset[0] = imageScale[0] * (imageSize[0] - anchor[0]);
|
||||
textOffset[1] = imageScale[1] * (imageSize[1] / 2 - anchor[1]);
|
||||
textAlign = 'left';
|
||||
}
|
||||
}
|
||||
@@ -1276,14 +1279,21 @@ function iconStyleParser(node, objectStack) {
|
||||
anchorXUnits = hotSpot.xunits;
|
||||
anchorYUnits = hotSpot.yunits;
|
||||
anchorOrigin = hotSpot.origin;
|
||||
} else if (src === DEFAULT_IMAGE_STYLE_SRC) {
|
||||
anchor = DEFAULT_IMAGE_STYLE_ANCHOR;
|
||||
anchorXUnits = DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS;
|
||||
anchorYUnits = DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS;
|
||||
} else if (/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(src)) {
|
||||
anchor = [0.5, 0];
|
||||
anchorXUnits = IconAnchorUnits.FRACTION;
|
||||
anchorYUnits = IconAnchorUnits.FRACTION;
|
||||
// Google hotspots from https://kml4earth.appspot.com/icons.html#notes
|
||||
if (/pushpin/.test(src)) {
|
||||
anchor = DEFAULT_IMAGE_STYLE_ANCHOR;
|
||||
anchorXUnits = DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS;
|
||||
anchorYUnits = DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS;
|
||||
} else if (/arrow-reverse/.test(src)) {
|
||||
anchor = [54, 42];
|
||||
anchorXUnits = DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS;
|
||||
anchorYUnits = DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS;
|
||||
} else if (/paddle/.test(src)) {
|
||||
anchor = [32, 1];
|
||||
anchorXUnits = DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS;
|
||||
anchorYUnits = DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS;
|
||||
}
|
||||
}
|
||||
|
||||
let offset;
|
||||
@@ -1306,16 +1316,13 @@ function iconStyleParser(node, objectStack) {
|
||||
rotation = toRadians(heading);
|
||||
}
|
||||
|
||||
let scale = /** @type {number|undefined} */ (object['scale']);
|
||||
const scale = /** @type {number|undefined} */ (object['scale']);
|
||||
|
||||
const color = /** @type {Array<number>|undefined} */ (object['color']);
|
||||
|
||||
if (drawIcon) {
|
||||
if (src == DEFAULT_IMAGE_STYLE_SRC) {
|
||||
size = DEFAULT_IMAGE_STYLE_SIZE;
|
||||
if (scale === undefined) {
|
||||
scale = DEFAULT_IMAGE_SCALE_MULTIPLIER;
|
||||
}
|
||||
}
|
||||
|
||||
const imageStyle = new Icon({
|
||||
@@ -1332,6 +1339,37 @@ function iconStyleParser(node, objectStack) {
|
||||
src: this.iconUrlFunction_(src),
|
||||
color: color,
|
||||
});
|
||||
|
||||
const imageScale = imageStyle.getScaleArray()[0];
|
||||
const imageSize = imageStyle.getSize();
|
||||
if (imageSize === null) {
|
||||
const imageState = imageStyle.getImageState();
|
||||
if (imageState === ImageState.IDLE || imageState === ImageState.LOADING) {
|
||||
const listener = function () {
|
||||
const imageState = imageStyle.getImageState();
|
||||
if (
|
||||
!(
|
||||
imageState === ImageState.IDLE ||
|
||||
imageState === ImageState.LOADING
|
||||
)
|
||||
) {
|
||||
const imageSize = imageStyle.getSize();
|
||||
if (imageSize && imageSize.length == 2) {
|
||||
const resizeScale = scaleForSize(imageSize);
|
||||
imageStyle.setScale(imageScale * resizeScale);
|
||||
}
|
||||
imageStyle.unlistenImageChange(listener);
|
||||
}
|
||||
};
|
||||
imageStyle.listenImageChange(listener);
|
||||
if (imageState === ImageState.IDLE) {
|
||||
imageStyle.load();
|
||||
}
|
||||
}
|
||||
} else if (imageSize.length == 2) {
|
||||
const resizeScale = scaleForSize(imageSize);
|
||||
imageStyle.setScale(imageScale * resizeScale);
|
||||
}
|
||||
styleObject['imageStyle'] = imageStyle;
|
||||
} else {
|
||||
// handle the case when we explicitly want to draw no icon.
|
||||
@@ -1882,8 +1920,12 @@ function readStyle(node, objectStack) {
|
||||
const geometry = feature.getGeometry();
|
||||
const type = geometry.getType();
|
||||
if (type === GeometryType.GEOMETRY_COLLECTION) {
|
||||
const collection =
|
||||
/** @type {import("../geom/GeometryCollection").default} */ (
|
||||
geometry
|
||||
);
|
||||
return new GeometryCollection(
|
||||
geometry
|
||||
collection
|
||||
.getGeometriesArrayRecursive()
|
||||
.filter(function (geometry) {
|
||||
const type = geometry.getType();
|
||||
@@ -1911,8 +1953,12 @@ function readStyle(node, objectStack) {
|
||||
const geometry = feature.getGeometry();
|
||||
const type = geometry.getType();
|
||||
if (type === GeometryType.GEOMETRY_COLLECTION) {
|
||||
const collection =
|
||||
/** @type {import("../geom/GeometryCollection").default} */ (
|
||||
geometry
|
||||
);
|
||||
return new GeometryCollection(
|
||||
geometry
|
||||
collection
|
||||
.getGeometriesArrayRecursive()
|
||||
.filter(function (geometry) {
|
||||
const type = geometry.getType();
|
||||
@@ -2609,7 +2655,15 @@ function writeIconStyle(node, style, objectStack) {
|
||||
|
||||
properties['Icon'] = iconProperties;
|
||||
|
||||
const scale = style.getScale();
|
||||
let scale = style.getScaleArray()[0];
|
||||
let imageSize = size;
|
||||
if (imageSize === null) {
|
||||
imageSize = DEFAULT_IMAGE_STYLE_SIZE;
|
||||
}
|
||||
if (imageSize.length == 2) {
|
||||
const resizeScale = scaleForSize(imageSize);
|
||||
scale = scale / resizeScale;
|
||||
}
|
||||
if (scale !== 1) {
|
||||
properties['scale'] = scale;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,11 @@ class MVT extends FeatureFormat {
|
||||
* @type {string}
|
||||
*/
|
||||
this.idProperty_ = options.idProperty;
|
||||
|
||||
this.supportedMediaTypes = [
|
||||
'application/vnd.mapbox-vector-tile',
|
||||
'application/x-protobuf',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,8 +24,8 @@ import {assert} from '../asserts.js';
|
||||
import {assign} from '../obj.js';
|
||||
import {get as getProjection} from '../proj.js';
|
||||
import {
|
||||
readNonNegativeInteger,
|
||||
readNonNegativeIntegerString,
|
||||
readPositiveInteger,
|
||||
writeStringTextNode,
|
||||
} from './xsd.js';
|
||||
|
||||
@@ -51,14 +51,14 @@ const FEATURE_COLLECTION_PARSERS = {
|
||||
*/
|
||||
const TRANSACTION_SUMMARY_PARSERS = {
|
||||
'http://www.opengis.net/wfs': {
|
||||
'totalInserted': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'totalUpdated': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'totalDeleted': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'totalInserted': makeObjectPropertySetter(readPositiveInteger),
|
||||
'totalUpdated': makeObjectPropertySetter(readPositiveInteger),
|
||||
'totalDeleted': makeObjectPropertySetter(readPositiveInteger),
|
||||
},
|
||||
'http://www.opengis.net/wfs/2.0': {
|
||||
'totalInserted': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'totalUpdated': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'totalDeleted': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'totalInserted': makeObjectPropertySetter(readPositiveInteger),
|
||||
'totalUpdated': makeObjectPropertySetter(readPositiveInteger),
|
||||
'totalDeleted': makeObjectPropertySetter(readPositiveInteger),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
readBooleanString,
|
||||
readDecimal,
|
||||
readDecimalString,
|
||||
readNonNegativeInteger,
|
||||
readNonNegativeIntegerString,
|
||||
readPositiveInteger,
|
||||
readString,
|
||||
} from './xsd.js';
|
||||
import {readHref} from './xlink.js';
|
||||
@@ -94,9 +94,9 @@ const SERVICE_PARSERS = makeStructureNS(NAMESPACE_URIS, {
|
||||
'ContactInformation': makeObjectPropertySetter(readContactInformation),
|
||||
'Fees': makeObjectPropertySetter(readString),
|
||||
'AccessConstraints': makeObjectPropertySetter(readString),
|
||||
'LayerLimit': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'MaxWidth': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'MaxHeight': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'LayerLimit': makeObjectPropertySetter(readPositiveInteger),
|
||||
'MaxWidth': makeObjectPropertySetter(readPositiveInteger),
|
||||
'MaxHeight': makeObjectPropertySetter(readPositiveInteger),
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
makeStructureNS,
|
||||
pushParseAndPop,
|
||||
} from '../xml.js';
|
||||
import {readDecimal, readNonNegativeInteger, readString} from './xsd.js';
|
||||
import {readDecimal, readPositiveInteger, readString} from './xsd.js';
|
||||
import {readHref} from './xlink.js';
|
||||
|
||||
/**
|
||||
@@ -150,10 +150,10 @@ const TMS_LIMITS_LIST_PARSERS = makeStructureNS(NAMESPACE_URIS, {
|
||||
// @ts-ignore
|
||||
const TMS_LIMITS_PARSERS = makeStructureNS(NAMESPACE_URIS, {
|
||||
'TileMatrix': makeObjectPropertySetter(readString),
|
||||
'MinTileRow': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'MaxTileRow': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'MinTileCol': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'MaxTileCol': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'MinTileRow': makeObjectPropertySetter(readPositiveInteger),
|
||||
'MaxTileRow': makeObjectPropertySetter(readPositiveInteger),
|
||||
'MinTileCol': makeObjectPropertySetter(readPositiveInteger),
|
||||
'MaxTileCol': makeObjectPropertySetter(readPositiveInteger),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -210,10 +210,10 @@ const TM_PARSERS = makeStructureNS(
|
||||
{
|
||||
'TopLeftCorner': makeObjectPropertySetter(readCoordinates),
|
||||
'ScaleDenominator': makeObjectPropertySetter(readDecimal),
|
||||
'TileWidth': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'TileHeight': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'MatrixWidth': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'MatrixHeight': makeObjectPropertySetter(readNonNegativeInteger),
|
||||
'TileWidth': makeObjectPropertySetter(readPositiveInteger),
|
||||
'TileHeight': makeObjectPropertySetter(readPositiveInteger),
|
||||
'MatrixWidth': makeObjectPropertySetter(readPositiveInteger),
|
||||
'MatrixHeight': makeObjectPropertySetter(readPositiveInteger),
|
||||
},
|
||||
makeStructureNS(OWS_NAMESPACE_URIS, {
|
||||
'Identifier': makeObjectPropertySetter(readString),
|
||||
|
||||
@@ -63,7 +63,7 @@ export function readDecimalString(string) {
|
||||
* @param {Node} node Node.
|
||||
* @return {number|undefined} Non negative integer.
|
||||
*/
|
||||
export function readNonNegativeInteger(node) {
|
||||
export function readPositiveInteger(node) {
|
||||
const s = getAllTextContent(node, false);
|
||||
return readNonNegativeIntegerString(s);
|
||||
}
|
||||
|
||||
@@ -16,18 +16,16 @@
|
||||
* @template T
|
||||
*/
|
||||
export function forEach(flatCoordinates, offset, end, stride, callback) {
|
||||
const point1 = [flatCoordinates[offset], flatCoordinates[offset + 1]];
|
||||
const point2 = [];
|
||||
let ret;
|
||||
for (; offset + stride < end; offset += stride) {
|
||||
point2[0] = flatCoordinates[offset + stride];
|
||||
point2[1] = flatCoordinates[offset + stride + 1];
|
||||
ret = callback(point1, point2);
|
||||
offset += stride;
|
||||
for (; offset < end; offset += stride) {
|
||||
ret = callback(
|
||||
flatCoordinates.slice(offset - stride, offset),
|
||||
flatCoordinates.slice(offset, offset + stride)
|
||||
);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
point1[0] = point2[0];
|
||||
point1[1] = point2[1];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export class DragBoxEvent extends Event {
|
||||
* @typedef {import("../Observable").OnSignature<import("../Observable").EventTypes, import("../events/Event.js").default, Return> &
|
||||
* import("../Observable").OnSignature<import("../ObjectEventType").Types|
|
||||
* 'change:active', import("../Object").ObjectEvent, Return> &
|
||||
* import("../Observable").OnSignature<'boxcancel'|'boxdrag'|'boxend', DragBoxEvent, Return> &
|
||||
* import("../Observable").OnSignature<'boxcancel'|'boxdrag'|'boxend'|'boxstart', DragBoxEvent, Return> &
|
||||
* import("../Observable").CombinedOnSignature<import("../Observable").EventTypes|import("../ObjectEventType").Types|
|
||||
* 'change:active'|'boxcancel'|'boxdrag'|'boxend', Return>} DragBoxOnSignature
|
||||
*/
|
||||
|
||||
@@ -1247,7 +1247,7 @@ export function createBox() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the drawing mode. The mode for mult-part geometries is the same as for
|
||||
* Get the drawing mode. The mode for multi-part geometries is the same as for
|
||||
* their single-part cousins.
|
||||
* @param {import("../geom/GeometryType.js").default} type Geometry type.
|
||||
* @return {Mode} Drawing mode.
|
||||
|
||||
@@ -1167,7 +1167,11 @@ class Modify extends PointerInteraction {
|
||||
map.forEachFeatureAtPixel(
|
||||
pixel,
|
||||
(feature, layer, geometry) => {
|
||||
geometry = geometry || feature.getGeometry();
|
||||
geometry =
|
||||
geometry ||
|
||||
/** @type {import("../geom/SimpleGeometry").default} */ (
|
||||
feature.getGeometry()
|
||||
);
|
||||
if (
|
||||
geometry.getType() === GeometryType.POINT &&
|
||||
includes(this.features_.getArray(), feature)
|
||||
|
||||
@@ -30,7 +30,7 @@ const SelectEventType = {
|
||||
* {@link module:ol/render/Feature} and an
|
||||
* {@link module:ol/layer/Layer} and returns `true` if the feature may be
|
||||
* selected or `false` otherwise.
|
||||
* @typedef {function(import("../Feature.js").FeatureLike, import("../layer/Layer.js").default):boolean} FilterFunction
|
||||
* @typedef {function(import("../Feature.js").FeatureLike, import("../layer/Layer.js").default<import("../source/Source").default>):boolean} FilterFunction
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -49,7 +49,7 @@ const SelectEventType = {
|
||||
* feature removes all from the selection.
|
||||
* See `toggle`, `add`, `remove` options for adding/removing extra features to/
|
||||
* from the selection.
|
||||
* @property {Array<import("../layer/Layer.js").default>|function(import("../layer/Layer.js").default): boolean} [layers]
|
||||
* @property {Array<import("../layer/Layer.js").default>|function(import("../layer/Layer.js").default<import("../source/Source").default>): boolean} [layers]
|
||||
* A list of layers from which features should be selected. Alternatively, a
|
||||
* filter function can be provided. The function will be called for each layer
|
||||
* in the map and should return `true` for layers that you want to be
|
||||
@@ -252,7 +252,7 @@ class Select extends Interaction {
|
||||
*/
|
||||
this.features_ = options.features || new Collection();
|
||||
|
||||
/** @type {function(import("../layer/Layer.js").default): boolean} */
|
||||
/** @type {function(import("../layer/Layer.js").default<import("../source/Source").default>): boolean} */
|
||||
let layerFilter;
|
||||
if (options.layers) {
|
||||
if (typeof options.layers === 'function') {
|
||||
@@ -269,7 +269,7 @@ class Select extends Interaction {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {function(import("../layer/Layer.js").default): boolean}
|
||||
* @type {function(import("../layer/Layer.js").default<import("../source/Source").default>): boolean}
|
||||
*/
|
||||
this.layerFilter_ = layerFilter;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ const TranslateEventType = {
|
||||
* {@link module:ol/render/Feature} and an
|
||||
* {@link module:ol/layer/Layer} and returns `true` if the feature may be
|
||||
* translated or `false` otherwise.
|
||||
* @typedef {function(import("../Feature.js").FeatureLike, import("../layer/Layer.js").default):boolean} FilterFunction
|
||||
* @typedef {function(import("../Feature.js").FeatureLike, import("../layer/Layer.js").default<import("../source/Source").default>):boolean} FilterFunction
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -47,17 +47,17 @@ const TranslateEventType = {
|
||||
* takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a
|
||||
* boolean to indicate whether that event should be handled.
|
||||
* Default is {@link module:ol/events/condition.always}.
|
||||
* @property {Collection<import("../Feature.js").default>} [features] Only features contained in this collection will be able to be translated. If
|
||||
* not specified, all features on the map will be able to be translated.
|
||||
* @property {Array<import("../layer/Layer.js").default>|function(import("../layer/Layer.js").default): boolean} [layers] A list of layers from which features should be
|
||||
* @property {Collection<import("../Feature.js").default>} [features] Features contained in this collection will be able to be translated together.
|
||||
* @property {Array<import("../layer/Layer.js").default>|function(import("../layer/Layer.js").default<import("../source/Source").default>): boolean} [layers] A list of layers from which features should be
|
||||
* translated. Alternatively, a filter function can be provided. The
|
||||
* function will be called for each layer in the map and should return
|
||||
* `true` for layers that you want to be translatable. If the option is
|
||||
* absent, all visible layers will be considered translatable.
|
||||
* Not used if `features` is provided.
|
||||
* @property {FilterFunction} [filter] A function
|
||||
* that takes an {@link module:ol/Feature} and an
|
||||
* {@link module:ol/layer/Layer} and returns `true` if the feature may be
|
||||
* translated or `false` otherwise.
|
||||
* translated or `false` otherwise. Not used if `features` is provided.
|
||||
* @property {number} [hitTolerance=0] Hit-detection tolerance. Pixels inside the radius around the given position
|
||||
* will be checked for features.
|
||||
*/
|
||||
@@ -123,6 +123,9 @@ export class TranslateEvent extends Event {
|
||||
/**
|
||||
* @classdesc
|
||||
* Interaction for translating (moving) features.
|
||||
* If you want to translate multiple features in a single action (for example,
|
||||
* the collection used by a select interaction), construct the interaction with
|
||||
* the `features` option.
|
||||
*
|
||||
* @fires TranslateEvent
|
||||
* @api
|
||||
@@ -171,9 +174,9 @@ class Translate extends PointerInteraction {
|
||||
*/
|
||||
this.features_ = options.features !== undefined ? options.features : null;
|
||||
|
||||
/** @type {function(import("../layer/Layer.js").default): boolean} */
|
||||
/** @type {function(import("../layer/Layer.js").default<import("../source/Source").default>): boolean} */
|
||||
let layerFilter;
|
||||
if (options.layers) {
|
||||
if (options.layers && !this.features_) {
|
||||
if (typeof options.layers === 'function') {
|
||||
layerFilter = options.layers;
|
||||
} else {
|
||||
@@ -188,7 +191,7 @@ class Translate extends PointerInteraction {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {function(import("../layer/Layer.js").default): boolean}
|
||||
* @type {function(import("../layer/Layer.js").default<import("../source/Source").default>): boolean}
|
||||
*/
|
||||
this.layerFilter_ = layerFilter;
|
||||
|
||||
@@ -196,7 +199,7 @@ class Translate extends PointerInteraction {
|
||||
* @private
|
||||
* @type {FilterFunction}
|
||||
*/
|
||||
this.filter_ = options.filter ? options.filter : TRUE;
|
||||
this.filter_ = options.filter && !this.features_ ? options.filter : TRUE;
|
||||
|
||||
/**
|
||||
* @private
|
||||
|
||||
@@ -13,3 +13,4 @@ export {default as Vector} from './layer/Vector.js';
|
||||
export {default as VectorImage} from './layer/VectorImage.js';
|
||||
export {default as VectorTile} from './layer/VectorTile.js';
|
||||
export {default as WebGLPoints} from './layer/WebGLPoints.js';
|
||||
export {default as WebGLTile} from './layer/WebGLTile.js';
|
||||
|
||||
@@ -13,6 +13,14 @@ import {getIntersection} from '../extent.js';
|
||||
import {getUid} from '../util.js';
|
||||
import {listen, unlistenByKey} from '../events.js';
|
||||
|
||||
/***
|
||||
* @template Return
|
||||
* @typedef {import("../Observable").OnSignature<import("../Observable").EventTypes, import("../events/Event.js").default, Return> &
|
||||
* import("../Observable").OnSignature<import("./Base").BaseLayerObjectEventTypes|
|
||||
* 'change:layers', import("../Object").ObjectEvent, Return> &
|
||||
* import("../Observable").CombinedOnSignature<import("../Observable").EventTypes|import("./Base").BaseLayerObjectEventTypes|'change:layers', Return>} GroupOnSignature
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Options
|
||||
* @property {number} [opacity=1] Opacity (0, 1).
|
||||
@@ -64,6 +72,21 @@ class LayerGroup extends BaseLayer {
|
||||
|
||||
super(baseOptions);
|
||||
|
||||
/***
|
||||
* @type {GroupOnSignature<import("../Observable.js").OnReturn>}
|
||||
*/
|
||||
this.on;
|
||||
|
||||
/***
|
||||
* @type {GroupOnSignature<import("../Observable.js").OnReturn>}
|
||||
*/
|
||||
this.once;
|
||||
|
||||
/***
|
||||
* @type {GroupOnSignature<void>}
|
||||
*/
|
||||
this.un;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Array<import("../events.js").EventsKey>}
|
||||
|
||||
323
src/ol/layer/WebGLTile.js
Normal file
323
src/ol/layer/WebGLTile.js
Normal file
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* @module ol/layer/WebGLTile
|
||||
*/
|
||||
import BaseTileLayer from './BaseTile.js';
|
||||
import WebGLTileLayerRenderer, {
|
||||
Attributes,
|
||||
Uniforms,
|
||||
} from '../renderer/webgl/TileLayer.js';
|
||||
import {
|
||||
ValueTypes,
|
||||
expressionToGlsl,
|
||||
getStringNumberEquivalent,
|
||||
uniformNameForVariable,
|
||||
} from '../style/expressions.js';
|
||||
import {assign} from '../obj.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Style
|
||||
* Translates tile data to rendered pixels.
|
||||
*
|
||||
* @property {Object<string, number>} [variables] Style variables. Each variable must hold a number. These
|
||||
* variables can be used in the `color`, `brightness`, `contrast`, `exposure`, `saturation` and `gamma`
|
||||
* {@link import("../style/expressions.js").ExpressionValue expressions}, using the `['var', 'varName']` operator.
|
||||
* To update style variables, use the {@link import("./WebGLTile.js").default#updateStyleVariables} method.
|
||||
* @property {import("../style/expressions.js").ExpressionValue} [color] An expression applied to color values.
|
||||
* @property {import("../style/expressions.js").ExpressionValue} [brightness=0] Value used to decrease or increase
|
||||
* the layer brightness. Values range from -1 to 1.
|
||||
* @property {import("../style/expressions.js").ExpressionValue} [contrast=0] Value used to decrease or increase
|
||||
* the layer contrast. Values range from -1 to 1.
|
||||
* @property {import("../style/expressions.js").ExpressionValue} [exposure=0] Value used to decrease or increase
|
||||
* the layer exposure. Values range from -1 to 1.
|
||||
* @property {import("../style/expressions.js").ExpressionValue} [saturation=0] Value used to decrease or increase
|
||||
* the layer saturation. Values range from -1 to 1.
|
||||
* @property {import("../style/expressions.js").ExpressionValue} [gamma=1] Apply a gamma correction to the layer.
|
||||
* Values range from 0 to infinity.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Options
|
||||
* @property {Style} [style] Style to apply to the layer.
|
||||
* @property {string} [className='ol-layer'] A CSS class name to set to the layer element.
|
||||
* @property {number} [opacity=1] Opacity (0, 1).
|
||||
* @property {boolean} [visible=true] Visibility.
|
||||
* @property {import("../extent.js").Extent} [extent] The bounding extent for layer rendering. The layer will not be
|
||||
* rendered outside of this extent.
|
||||
* @property {number} [zIndex] The z-index for layer rendering. At rendering time, the layers
|
||||
* will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed
|
||||
* for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()`
|
||||
* method was used.
|
||||
* @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be
|
||||
* visible.
|
||||
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
|
||||
* be visible.
|
||||
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
|
||||
* visible.
|
||||
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
|
||||
* be visible.
|
||||
* @property {number} [preload=0] Preload. Load low-resolution tiles up to `preload` levels. `0`
|
||||
* means no preloading.
|
||||
* @property {import("../source/Tile.js").default} [source] Source for this layer.
|
||||
* @property {import("../PluggableMap.js").default} [map] Sets the layer as overlay on a map. The map will not manage
|
||||
* this layer in its layers collection, and the layer will be rendered on top. This is useful for
|
||||
* temporary layers. The standard way to add a layer to a map and have it managed by the map is to
|
||||
* use {@link module:ol/Map#addLayer}.
|
||||
* @property {boolean} [useInterimTilesOnError=true] Use interim tiles on error.
|
||||
* @property {number} [cacheSize=512] The internal texture cache size. This needs to be large enough to render
|
||||
* two zoom levels worth of tiles.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ParsedStyle
|
||||
* @property {string} vertexShader The vertex shader.
|
||||
* @property {string} fragmentShader The fragment shader.
|
||||
* @property {Object<string,import("../webgl/Helper.js").UniformValue>} uniforms Uniform definitions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Style} style The layer style.
|
||||
* @param {number} [bandCount] The number of bands.
|
||||
* @return {ParsedStyle} Shaders and uniforms generated from the style.
|
||||
*/
|
||||
function parseStyle(style, bandCount) {
|
||||
const vertexShader = `
|
||||
attribute vec2 ${Attributes.TEXTURE_COORD};
|
||||
uniform mat4 ${Uniforms.TILE_TRANSFORM};
|
||||
uniform float ${Uniforms.DEPTH};
|
||||
|
||||
varying vec2 v_textureCoord;
|
||||
|
||||
void main() {
|
||||
v_textureCoord = ${Attributes.TEXTURE_COORD};
|
||||
gl_Position = ${Uniforms.TILE_TRANSFORM} * vec4(${Attributes.TEXTURE_COORD}, ${Uniforms.DEPTH}, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* @type {import("../style/expressions.js").ParsingContext}
|
||||
*/
|
||||
const context = {
|
||||
inFragmentShader: true,
|
||||
variables: [],
|
||||
attributes: [],
|
||||
stringLiteralsMap: {},
|
||||
bandCount: bandCount,
|
||||
};
|
||||
|
||||
const pipeline = [];
|
||||
|
||||
if (style.color !== undefined) {
|
||||
const color = expressionToGlsl(context, style.color, ValueTypes.COLOR);
|
||||
pipeline.push(`color = ${color};`);
|
||||
}
|
||||
|
||||
if (style.contrast !== undefined) {
|
||||
const contrast = expressionToGlsl(
|
||||
context,
|
||||
style.contrast,
|
||||
ValueTypes.NUMBER
|
||||
);
|
||||
pipeline.push(
|
||||
`color.rgb = clamp((${contrast} + 1.0) * color.rgb - (${contrast} / 2.0), vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));`
|
||||
);
|
||||
}
|
||||
|
||||
if (style.exposure !== undefined) {
|
||||
const exposure = expressionToGlsl(
|
||||
context,
|
||||
style.exposure,
|
||||
ValueTypes.NUMBER
|
||||
);
|
||||
pipeline.push(
|
||||
`color.rgb = clamp((${exposure} + 1.0) * color.rgb, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));`
|
||||
);
|
||||
}
|
||||
|
||||
if (style.saturation !== undefined) {
|
||||
const saturation = expressionToGlsl(
|
||||
context,
|
||||
style.saturation,
|
||||
ValueTypes.NUMBER
|
||||
);
|
||||
pipeline.push(`
|
||||
float saturation = ${saturation} + 1.0;
|
||||
float sr = (1.0 - saturation) * 0.2126;
|
||||
float sg = (1.0 - saturation) * 0.7152;
|
||||
float sb = (1.0 - saturation) * 0.0722;
|
||||
mat3 saturationMatrix = mat3(
|
||||
sr + saturation, sr, sr,
|
||||
sg, sg + saturation, sg,
|
||||
sb, sb, sb + saturation
|
||||
);
|
||||
color.rgb = clamp(saturationMatrix * color.rgb, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));
|
||||
`);
|
||||
}
|
||||
|
||||
if (style.gamma !== undefined) {
|
||||
const gamma = expressionToGlsl(context, style.gamma, ValueTypes.NUMBER);
|
||||
pipeline.push(`color.rgb = pow(color.rgb, vec3(1.0 / ${gamma}));`);
|
||||
}
|
||||
|
||||
if (style.brightness !== undefined) {
|
||||
const brightness = expressionToGlsl(
|
||||
context,
|
||||
style.brightness,
|
||||
ValueTypes.NUMBER
|
||||
);
|
||||
pipeline.push(
|
||||
`color.rgb = clamp(color.rgb + ${brightness}, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));`
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {Object<string,import("../webgl/Helper").UniformValue>} */
|
||||
const uniforms = {};
|
||||
|
||||
const numVariables = context.variables.length;
|
||||
if (numVariables > 1 && !style.variables) {
|
||||
throw new Error(
|
||||
`Missing variables in style (expected ${context.variables})`
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < numVariables; ++i) {
|
||||
const variableName = context.variables[i];
|
||||
if (!(variableName in style.variables)) {
|
||||
throw new Error(`Missing '${variableName}' in style variables`);
|
||||
}
|
||||
const uniformName = uniformNameForVariable(variableName);
|
||||
uniforms[uniformName] = function () {
|
||||
let value = style.variables[variableName];
|
||||
if (typeof value === 'string') {
|
||||
value = getStringNumberEquivalent(context, value);
|
||||
}
|
||||
return value !== undefined ? value : -9999999; // to avoid matching with the first string literal
|
||||
};
|
||||
}
|
||||
|
||||
const uniformDeclarations = Object.keys(uniforms).map(function (name) {
|
||||
return `uniform float ${name};`;
|
||||
});
|
||||
|
||||
const textureCount = Math.ceil(bandCount / 4);
|
||||
const colorAssignments = new Array(textureCount);
|
||||
for (let textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
|
||||
const uniformName = Uniforms.TILE_TEXTURE_PREFIX + textureIndex;
|
||||
uniformDeclarations.push(`uniform sampler2D ${uniformName};`);
|
||||
colorAssignments[
|
||||
textureIndex
|
||||
] = `vec4 color${textureIndex} = texture2D(${uniformName}, v_textureCoord);`;
|
||||
}
|
||||
|
||||
const fragmentShader = `
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
#else
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
varying vec2 v_textureCoord;
|
||||
uniform float ${Uniforms.TRANSITION_ALPHA};
|
||||
uniform float ${Uniforms.TEXTURE_PIXEL_WIDTH};
|
||||
uniform float ${Uniforms.TEXTURE_PIXEL_HEIGHT};
|
||||
uniform float ${Uniforms.RESOLUTION};
|
||||
uniform float ${Uniforms.ZOOM};
|
||||
|
||||
${uniformDeclarations.join('\n')}
|
||||
|
||||
void main() {
|
||||
${colorAssignments.join('\n')}
|
||||
|
||||
vec4 color = color0;
|
||||
|
||||
${pipeline.join('\n')}
|
||||
|
||||
if (color.a == 0.0) {
|
||||
discard;
|
||||
}
|
||||
|
||||
gl_FragColor = color;
|
||||
gl_FragColor.rgb *= gl_FragColor.a;
|
||||
gl_FragColor *= ${Uniforms.TRANSITION_ALPHA};
|
||||
}`;
|
||||
|
||||
return {
|
||||
vertexShader: vertexShader,
|
||||
fragmentShader: fragmentShader,
|
||||
uniforms: uniforms,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* For layer sources that provide pre-rendered, tiled images in grids that are
|
||||
* organized by zoom levels for specific resolutions.
|
||||
* Note that any property set in the options is set as a {@link module:ol/Object~BaseObject}
|
||||
* property on the layer object; for example, setting `title: 'My Title'` in the
|
||||
* options means that `title` is observable, and has get/set accessors.
|
||||
*
|
||||
* @extends BaseTileLayer<import("../source/DataTile.js").default|import("../source/TileImage.js").default>
|
||||
* @api
|
||||
*/
|
||||
class WebGLTileLayer extends BaseTileLayer {
|
||||
/**
|
||||
* @param {Options} opt_options Tile layer options.
|
||||
*/
|
||||
constructor(opt_options) {
|
||||
const options = opt_options ? assign({}, opt_options) : {};
|
||||
|
||||
const style = options.style || {};
|
||||
delete options.style;
|
||||
|
||||
const cacheSize = options.cacheSize;
|
||||
delete options.cacheSize;
|
||||
|
||||
super(options);
|
||||
|
||||
/**
|
||||
* @type {Style}
|
||||
* @private
|
||||
*/
|
||||
this.style_ = style;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.cacheSize_ = cacheSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a renderer for this layer.
|
||||
* @return {import("../renderer/Layer.js").default} A layer renderer.
|
||||
* @protected
|
||||
*/
|
||||
createRenderer() {
|
||||
const source = this.getSource();
|
||||
const parsedStyle = parseStyle(
|
||||
this.style_,
|
||||
'bandCount' in source ? source.bandCount : 4
|
||||
);
|
||||
|
||||
this.styleVariables_ = this.style_.variables || {};
|
||||
|
||||
return new WebGLTileLayerRenderer(this, {
|
||||
vertexShader: parsedStyle.vertexShader,
|
||||
fragmentShader: parsedStyle.fragmentShader,
|
||||
uniforms: parsedStyle.uniforms,
|
||||
className: this.getClassName(),
|
||||
cacheSize: this.cacheSize_,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update any variables used by the layer style and trigger a re-render.
|
||||
* @param {Object<string, number>} variables Variables to update.
|
||||
* @api
|
||||
*/
|
||||
updateStyleVariables(variables) {
|
||||
assign(this.styleVariables_, variables);
|
||||
this.changed();
|
||||
}
|
||||
}
|
||||
|
||||
export default WebGLTileLayer;
|
||||
107
src/ol/net.js
107
src/ol/net.js
@@ -41,3 +41,110 @@ export function jsonp(url, callback, opt_errback, opt_callbackParam) {
|
||||
};
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
}
|
||||
|
||||
export class ResponseError extends Error {
|
||||
/**
|
||||
* @param {XMLHttpRequest} response The XHR object.
|
||||
*/
|
||||
constructor(response) {
|
||||
const message = 'Unexpected response status: ' + response.status;
|
||||
super(message);
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'ResponseError';
|
||||
|
||||
/**
|
||||
* @type {XMLHttpRequest}
|
||||
*/
|
||||
this.response = response;
|
||||
}
|
||||
}
|
||||
|
||||
export class ClientError extends Error {
|
||||
/**
|
||||
* @param {XMLHttpRequest} client The XHR object.
|
||||
*/
|
||||
constructor(client) {
|
||||
super('Failed to issue request');
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'ClientError';
|
||||
|
||||
/**
|
||||
* @type {XMLHttpRequest}
|
||||
*/
|
||||
this.client = client;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url The URL.
|
||||
* @return {Promise<Object>} A promise that resolves to the JSON response.
|
||||
*/
|
||||
export function getJSON(url) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
/**
|
||||
* @param {ProgressEvent<XMLHttpRequest>} event The load event.
|
||||
*/
|
||||
function onLoad(event) {
|
||||
const client = event.target;
|
||||
// status will be 0 for file:// urls
|
||||
if (!client.status || (client.status >= 200 && client.status < 300)) {
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(client.responseText);
|
||||
} catch (err) {
|
||||
const message = 'Error parsing response text as JSON: ' + err.message;
|
||||
reject(new Error(message));
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
return;
|
||||
}
|
||||
|
||||
reject(new ResponseError(client));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProgressEvent<XMLHttpRequest>} event The error event.
|
||||
*/
|
||||
function onError(event) {
|
||||
reject(new ClientError(event.target));
|
||||
}
|
||||
|
||||
const client = new XMLHttpRequest();
|
||||
client.addEventListener('load', onLoad);
|
||||
client.addEventListener('error', onError);
|
||||
client.open('GET', url);
|
||||
client.setRequestHeader('Accept', 'application/json');
|
||||
client.send();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} base The base URL.
|
||||
* @param {string} url The potentially relative URL.
|
||||
* @return {string} The full URL.
|
||||
*/
|
||||
export function resolveUrl(base, url) {
|
||||
if (url.indexOf('://') >= 0) {
|
||||
return url;
|
||||
}
|
||||
return new URL(url, base).href;
|
||||
}
|
||||
|
||||
let originalXHR;
|
||||
export function overrideXHR(xhr) {
|
||||
if (typeof XMLHttpRequest !== 'undefined') {
|
||||
originalXHR = XMLHttpRequest;
|
||||
}
|
||||
global.XMLHttpRequest = xhr;
|
||||
}
|
||||
|
||||
export function restoreXHR() {
|
||||
global.XMLHttpRequest = originalXHR;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
* @enum {string}
|
||||
*/
|
||||
const Units = {
|
||||
/**
|
||||
* Radians
|
||||
* @api
|
||||
*/
|
||||
RADIANS: 'radians',
|
||||
/**
|
||||
* Degrees
|
||||
* @api
|
||||
@@ -40,6 +45,26 @@ const Units = {
|
||||
USFEET: 'us-ft',
|
||||
};
|
||||
|
||||
/**
|
||||
* See http://duff.ess.washington.edu/data/raster/drg/docs/geotiff.txt
|
||||
* @type {Object<number, Units>}
|
||||
*/
|
||||
const unitByCode = {
|
||||
'9001': Units.METERS,
|
||||
'9002': Units.FEET,
|
||||
'9003': Units.USFEET,
|
||||
'9101': Units.RADIANS,
|
||||
'9102': Units.DEGREES,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} code Unit code.
|
||||
* @return {Units} Units.
|
||||
*/
|
||||
export function fromCode(code) {
|
||||
return unitByCode[code];
|
||||
}
|
||||
|
||||
/**
|
||||
* Meters per unit lookup table.
|
||||
* @const
|
||||
@@ -48,6 +73,7 @@ const Units = {
|
||||
*/
|
||||
export const METERS_PER_UNIT = {};
|
||||
// use the radius of the Normal sphere
|
||||
METERS_PER_UNIT[Units.RADIANS] = 6370997 / (2 * Math.PI);
|
||||
METERS_PER_UNIT[Units.DEGREES] = (2 * Math.PI * 6370997) / 360;
|
||||
METERS_PER_UNIT[Units.FEET] = 0.3048;
|
||||
METERS_PER_UNIT[Units.METERS] = 1;
|
||||
|
||||
@@ -71,6 +71,7 @@ export const PROJECTIONS = [
|
||||
new EPSG3857Projection('EPSG:102100'),
|
||||
new EPSG3857Projection('EPSG:102113'),
|
||||
new EPSG3857Projection('EPSG:900913'),
|
||||
new EPSG3857Projection('http://www.opengis.net/def/crs/EPSG/0/3857'),
|
||||
new EPSG3857Projection('http://www.opengis.net/gml/srs/epsg.xml#3857'),
|
||||
];
|
||||
|
||||
|
||||
@@ -63,5 +63,7 @@ export const PROJECTIONS = [
|
||||
new EPSG4326Projection('EPSG:4326', 'neu'),
|
||||
new EPSG4326Projection('urn:ogc:def:crs:OGC:1.3:CRS84'),
|
||||
new EPSG4326Projection('urn:ogc:def:crs:OGC:2:84'),
|
||||
new EPSG4326Projection('http://www.opengis.net/def/crs/OGC/1.3/CRS84', 'neu'),
|
||||
new EPSG4326Projection('http://www.opengis.net/gml/srs/epsg.xml#4326', 'neu'),
|
||||
new EPSG4326Projection('http://www.opengis.net/def/crs/EPSG/0/4326', 'neu'),
|
||||
];
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
createSafeCoordinateTransform,
|
||||
get,
|
||||
} from '../proj.js';
|
||||
import {assign} from '../obj.js';
|
||||
import {get as getTransform} from './transforms.js';
|
||||
|
||||
/**
|
||||
@@ -53,16 +52,10 @@ export function register(proj4) {
|
||||
const code2 = projCodes[j];
|
||||
const proj2 = get(code2);
|
||||
if (!getTransform(code1, code2)) {
|
||||
const def1 = proj4.defs(code1);
|
||||
const def2 = proj4.defs(code2);
|
||||
if (def1 === def2) {
|
||||
if (proj4.defs[code1] === proj4.defs[code2]) {
|
||||
addEquivalentProjections([proj1, proj2]);
|
||||
} else {
|
||||
// Reset axis because OpenLayers always uses x, y axis order
|
||||
const transform = proj4(
|
||||
assign({}, def1, {axis: undefined}),
|
||||
assign({}, def2, {axis: undefined})
|
||||
);
|
||||
const transform = proj4(code1, code2);
|
||||
addCoordinateTransforms(
|
||||
proj1,
|
||||
proj2,
|
||||
|
||||
@@ -249,8 +249,8 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
this.image_ = image;
|
||||
this.height_ = size[1];
|
||||
this.opacity_ = imageStyle.getOpacity();
|
||||
this.originX_ = origin[0];
|
||||
this.originY_ = origin[1];
|
||||
this.originX_ = origin[0] * this.imagePixelRatio_;
|
||||
this.originY_ = origin[1] * this.imagePixelRatio_;
|
||||
this.rotateWithView_ = imageStyle.getRotateWithView();
|
||||
this.rotation_ = imageStyle.getRotation();
|
||||
this.scale_ = imageStyle.getScaleArray();
|
||||
|
||||
@@ -153,9 +153,9 @@ class CompositeMapRenderer extends MapRenderer {
|
||||
* @param {import("../pixel.js").Pixel} pixel Pixel.
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState FrameState.
|
||||
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||
* @param {function(import("../layer/Layer.js").default, (Uint8ClampedArray|Uint8Array)): T} callback Layer
|
||||
* @param {function(import("../layer/Layer.js").default<import("../source/Source").default>, (Uint8ClampedArray|Uint8Array)): T} callback Layer
|
||||
* callback.
|
||||
* @param {function(import("../layer/Layer.js").default): boolean} layerFilter Layer filter
|
||||
* @param {function(import("../layer/Layer.js").default<import("../source/Source").default>): boolean} layerFilter Layer filter
|
||||
* function, only layers which are visible and for which this function
|
||||
* returns `true` will be tested for features. By default, all visible
|
||||
* layers will be tested.
|
||||
|
||||
@@ -175,9 +175,9 @@ class MapRenderer extends Disposable {
|
||||
* @param {import("../pixel.js").Pixel} pixel Pixel.
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState FrameState.
|
||||
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||
* @param {function(import("../layer/Layer.js").default, (Uint8ClampedArray|Uint8Array)): T} callback Layer
|
||||
* @param {function(import("../layer/Layer.js").default<import("../source/Source").default>, (Uint8ClampedArray|Uint8Array)): T} callback Layer
|
||||
* callback.
|
||||
* @param {function(import("../layer/Layer.js").default): boolean} layerFilter Layer filter
|
||||
* @param {function(import("../layer/Layer.js").default<import("../source/Source").default>): boolean} layerFilter Layer filter
|
||||
* function, only layers which are visible and for which this function
|
||||
* returns `true` will be tested for features. By default, all visible
|
||||
* layers will be tested.
|
||||
|
||||
@@ -13,7 +13,7 @@ import {getUid} from '../util.js';
|
||||
* unmanaged layers. The third is the {@link module:ol/geom/SimpleGeometry} of the feature. For features
|
||||
* with a GeometryCollection geometry, it will be the first detected geometry from the collection.
|
||||
* @template T
|
||||
* @typedef {function(import("../Feature.js").FeatureLike, import("../layer/Layer.js").default, import("../geom/SimpleGeometry.js").default): T} FeatureCallback
|
||||
* @typedef {function(import("../Feature.js").FeatureLike, import("../layer/Layer.js").default<import("../source/Source").default>, import("../geom/SimpleGeometry.js").default): T} FeatureCallback
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
541
src/ol/renderer/webgl/TileLayer.js
Normal file
541
src/ol/renderer/webgl/TileLayer.js
Normal file
@@ -0,0 +1,541 @@
|
||||
/**
|
||||
* @module ol/renderer/webgl/TileLayer
|
||||
*/
|
||||
import LRUCache from '../../structs/LRUCache.js';
|
||||
import State from '../../source/State.js';
|
||||
import TileRange from '../../TileRange.js';
|
||||
import TileState from '../../TileState.js';
|
||||
import TileTexture from '../../webgl/TileTexture.js';
|
||||
import WebGLArrayBuffer from '../../webgl/Buffer.js';
|
||||
import WebGLLayerRenderer from './Layer.js';
|
||||
import {AttributeType} from '../../webgl/Helper.js';
|
||||
import {ELEMENT_ARRAY_BUFFER, STATIC_DRAW} from '../../webgl.js';
|
||||
import {
|
||||
compose as composeTransform,
|
||||
create as createTransform,
|
||||
} from '../../transform.js';
|
||||
import {
|
||||
create as createMat4,
|
||||
fromTransform as mat4FromTransform,
|
||||
} from '../../vec/mat4.js';
|
||||
import {
|
||||
createOrUpdate as createTileCoord,
|
||||
getKeyZXY,
|
||||
getKey as getTileCoordKey,
|
||||
} from '../../tilecoord.js';
|
||||
import {fromUserExtent} from '../../proj.js';
|
||||
import {getIntersection} from '../../extent.js';
|
||||
import {getUid} from '../../util.js';
|
||||
import {isEmpty} from '../../extent.js';
|
||||
import {numberSafeCompareFunction} from '../../array.js';
|
||||
import {toSize} from '../../size.js';
|
||||
|
||||
export const Uniforms = {
|
||||
TILE_TEXTURE_PREFIX: 'u_tileTexture',
|
||||
TILE_TRANSFORM: 'u_tileTransform',
|
||||
TRANSITION_ALPHA: 'u_transitionAlpha',
|
||||
DEPTH: 'u_depth',
|
||||
TEXTURE_PIXEL_WIDTH: 'u_texturePixelWidth',
|
||||
TEXTURE_PIXEL_HEIGHT: 'u_texturePixelHeight',
|
||||
RESOLUTION: 'u_resolution',
|
||||
ZOOM: 'u_zoom',
|
||||
};
|
||||
|
||||
export const Attributes = {
|
||||
TEXTURE_COORD: 'a_textureCoord',
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {Array<import('../../webgl/Helper.js').AttributeDescription>}
|
||||
*/
|
||||
const attributeDescriptions = [
|
||||
{
|
||||
name: Attributes.TEXTURE_COORD,
|
||||
size: 2,
|
||||
type: AttributeType.FLOAT,
|
||||
},
|
||||
];
|
||||
|
||||
const empty = {};
|
||||
|
||||
/**
|
||||
* Transform a zoom level into a depth value ranging from -1 to 1.
|
||||
* @param {number} z A zoom level.
|
||||
* @return {number} A depth value.
|
||||
*/
|
||||
function depthForZ(z) {
|
||||
return 2 * (1 - 1 / (z + 1)) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tile texture to the lookup.
|
||||
* @param {Object<string, Array<import("../../webgl/TileTexture.js").default>>} tileTexturesByZ Lookup of
|
||||
* tile textures by zoom level.
|
||||
* @param {import("../../webgl/TileTexture.js").default} tileTexture A tile texture.
|
||||
* @param {number} z The zoom level.
|
||||
*/
|
||||
function addTileTextureToLookup(tileTexturesByZ, tileTexture, z) {
|
||||
if (!(z in tileTexturesByZ)) {
|
||||
tileTexturesByZ[z] = [];
|
||||
}
|
||||
tileTexturesByZ[z].push(tileTexture);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
* @return {import("../../extent.js").Extent} Extent.
|
||||
*/
|
||||
function getRenderExtent(frameState) {
|
||||
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
||||
let extent = frameState.extent;
|
||||
if (layerState.extent) {
|
||||
extent = getIntersection(
|
||||
extent,
|
||||
fromUserExtent(layerState.extent, frameState.viewState.projection)
|
||||
);
|
||||
}
|
||||
return extent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} Options
|
||||
* @property {string} vertexShader Vertex shader source.
|
||||
* @property {string} fragmentShader Fragment shader source.
|
||||
* @property {Object<string, import("../../webgl/Helper").UniformValue>} [uniforms] Additional uniforms
|
||||
* made available to shaders.
|
||||
* @property {string} [className='ol-layer'] A CSS class name to set to the canvas element.
|
||||
* @property {number} [cacheSize=512] The texture cache size.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* WebGL renderer for tile layers.
|
||||
* @api
|
||||
*/
|
||||
class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
||||
/**
|
||||
* @param {import("../../layer/WebGLTile.js").default} tileLayer Tile layer.
|
||||
* @param {Options} options Options.
|
||||
*/
|
||||
constructor(tileLayer, options) {
|
||||
super(tileLayer, {
|
||||
uniforms: options.uniforms,
|
||||
className: options.className,
|
||||
});
|
||||
|
||||
/**
|
||||
* This transform converts tile i, j coordinates to screen coordinates.
|
||||
* @type {import("../../transform.js").Transform}
|
||||
* @private
|
||||
*/
|
||||
this.tileTransform_ = createTransform();
|
||||
|
||||
/**
|
||||
* @type {Array<number>}
|
||||
* @private
|
||||
*/
|
||||
this.tempMat4_ = createMat4();
|
||||
|
||||
/**
|
||||
* @type {import("../../TileRange.js").default}
|
||||
* @private
|
||||
*/
|
||||
this.tempTileRange_ = new TileRange(0, 0, 0, 0);
|
||||
|
||||
/**
|
||||
* @type {import("../../tilecoord.js").TileCoord}
|
||||
* @private
|
||||
*/
|
||||
this.tempTileCoord_ = createTileCoord(0, 0, 0);
|
||||
|
||||
/**
|
||||
* @type {import("../../size.js").Size}
|
||||
* @private
|
||||
*/
|
||||
this.tempSize_ = [0, 0];
|
||||
|
||||
this.program_ = this.helper.getProgram(
|
||||
options.fragmentShader,
|
||||
options.vertexShader
|
||||
);
|
||||
|
||||
/**
|
||||
* Tiles are rendered as a quad with the following structure:
|
||||
*
|
||||
* [P3]---------[P2]
|
||||
* |` |
|
||||
* | ` B |
|
||||
* | ` |
|
||||
* | ` |
|
||||
* | A ` |
|
||||
* | ` |
|
||||
* [P0]---------[P1]
|
||||
*
|
||||
* Triangle A: P0, P1, P3
|
||||
* Triangle B: P1, P2, P3
|
||||
*/
|
||||
const indices = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, STATIC_DRAW);
|
||||
indices.fromArray([0, 1, 3, 1, 2, 3]);
|
||||
this.helper.flushBufferData(indices);
|
||||
this.indices_ = indices;
|
||||
|
||||
const cacheSize = options.cacheSize !== undefined ? options.cacheSize : 512;
|
||||
this.tileTextureCache_ = new LRUCache(cacheSize);
|
||||
|
||||
this.renderedOpacity_ = NaN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @param {import("../../Tile.js").default} tile Tile.
|
||||
* @return {boolean} Tile is drawable.
|
||||
*/
|
||||
isDrawableTile(tile) {
|
||||
const tileLayer = this.getLayer();
|
||||
const tileState = tile.getState();
|
||||
const useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
|
||||
return (
|
||||
tileState == TileState.LOADED ||
|
||||
tileState == TileState.EMPTY ||
|
||||
(tileState == TileState.ERROR && !useInterimTilesOnError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether render should be called.
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
* @return {boolean} Layer is ready to be rendered.
|
||||
*/
|
||||
prepareFrame(frameState) {
|
||||
if (isEmpty(getRenderExtent(frameState))) {
|
||||
return false;
|
||||
}
|
||||
const source = this.getLayer().getSource();
|
||||
if (!source) {
|
||||
return false;
|
||||
}
|
||||
return source.getState() === State.READY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the layer.
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
* @return {HTMLElement} The rendered element.
|
||||
*/
|
||||
renderFrame(frameState) {
|
||||
this.preRender(frameState);
|
||||
|
||||
const viewState = frameState.viewState;
|
||||
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
||||
const extent = getRenderExtent(frameState);
|
||||
const tileLayer = this.getLayer();
|
||||
const tileSource = tileLayer.getSource();
|
||||
const tileGrid = tileSource.getTileGridForProjection(viewState.projection);
|
||||
const z = tileGrid.getZForResolution(
|
||||
viewState.resolution,
|
||||
tileSource.zDirection
|
||||
);
|
||||
|
||||
/**
|
||||
* @type {Object<string, Array<import("../../webgl/TileTexture.js").default>>}
|
||||
*/
|
||||
const tileTexturesByZ = {};
|
||||
|
||||
const tileTextureCache = this.tileTextureCache_;
|
||||
const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
|
||||
|
||||
const tileSourceKey = getUid(tileSource);
|
||||
if (!(tileSourceKey in frameState.wantedTiles)) {
|
||||
frameState.wantedTiles[tileSourceKey] = {};
|
||||
}
|
||||
|
||||
const wantedTiles = frameState.wantedTiles[tileSourceKey];
|
||||
|
||||
const tileResolution = tileGrid.getResolution(z);
|
||||
|
||||
for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
||||
for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
||||
const tileCoord = createTileCoord(z, x, y, this.tempTileCoord_);
|
||||
const tileCoordKey = getTileCoordKey(tileCoord);
|
||||
|
||||
let tileTexture, tile;
|
||||
if (tileTextureCache.containsKey(tileCoordKey)) {
|
||||
tileTexture = tileTextureCache.get(tileCoordKey);
|
||||
tile = tileTexture.tile;
|
||||
}
|
||||
if (!tileTexture || tileTexture.tile.key !== tileSource.getKey()) {
|
||||
tile = tileSource.getTile(
|
||||
z,
|
||||
x,
|
||||
y,
|
||||
frameState.pixelRatio,
|
||||
viewState.projection
|
||||
);
|
||||
if (!tileTexture) {
|
||||
tileTexture = new TileTexture(tile, tileGrid, this.helper);
|
||||
tileTextureCache.set(tileCoordKey, tileTexture);
|
||||
} else {
|
||||
tileTexture.setTile(
|
||||
this.isDrawableTile(tile) ? tile : tile.getInterimTile()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
addTileTextureToLookup(tileTexturesByZ, tileTexture, z);
|
||||
|
||||
const tileQueueKey = tile.getKey();
|
||||
wantedTiles[tileQueueKey] = true;
|
||||
|
||||
if (tile.getState() === TileState.IDLE) {
|
||||
if (!frameState.tileQueue.isKeyQueued(tileQueueKey)) {
|
||||
frameState.tileQueue.enqueue([
|
||||
tile,
|
||||
tileSourceKey,
|
||||
tileGrid.getTileCoordCenter(tileCoord),
|
||||
tileResolution,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lookup of alpha values for tiles at the target rendering resolution
|
||||
* for tiles that are in transition. If a tile coord key is absent from
|
||||
* this lookup, the tile should be rendered at alpha 1.
|
||||
* @type {Object<string, number>}
|
||||
*/
|
||||
const alphaLookup = {};
|
||||
|
||||
const uid = getUid(this);
|
||||
const time = frameState.time;
|
||||
let blend = false;
|
||||
|
||||
// look for cached tiles to use if a target tile is not ready
|
||||
const tileTextures = tileTexturesByZ[z];
|
||||
for (let i = 0, ii = tileTextures.length; i < ii; ++i) {
|
||||
const tileTexture = tileTextures[i];
|
||||
const tile = tileTexture.tile;
|
||||
const tileCoord = tile.tileCoord;
|
||||
|
||||
if (tileTexture.loaded) {
|
||||
const alpha = tile.getAlpha(uid, time);
|
||||
if (alpha === 1) {
|
||||
// no need to look for alt tiles
|
||||
tile.endTransition(uid);
|
||||
continue;
|
||||
}
|
||||
blend = true;
|
||||
const tileCoordKey = getTileCoordKey(tileCoord);
|
||||
alphaLookup[tileCoordKey] = alpha;
|
||||
}
|
||||
|
||||
// first look for child tiles (at z + 1)
|
||||
const coveredByChildren = this.findAltTiles_(
|
||||
tileGrid,
|
||||
tileCoord,
|
||||
z + 1,
|
||||
tileTexturesByZ
|
||||
);
|
||||
|
||||
if (coveredByChildren) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// next look for parent tiles
|
||||
for (let parentZ = z - 1; parentZ >= tileGrid.minZoom; --parentZ) {
|
||||
const coveredByParent = this.findAltTiles_(
|
||||
tileGrid,
|
||||
tileCoord,
|
||||
parentZ,
|
||||
tileTexturesByZ
|
||||
);
|
||||
|
||||
if (coveredByParent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.helper.useProgram(this.program_);
|
||||
this.helper.prepareDraw(frameState, !blend);
|
||||
|
||||
const zs = Object.keys(tileTexturesByZ)
|
||||
.map(Number)
|
||||
.sort(numberSafeCompareFunction);
|
||||
|
||||
const gl = this.helper.getGL();
|
||||
|
||||
const centerX = viewState.center[0];
|
||||
const centerY = viewState.center[1];
|
||||
|
||||
for (let j = 0, jj = zs.length; j < jj; ++j) {
|
||||
const tileZ = zs[j];
|
||||
const tileResolution = tileGrid.getResolution(tileZ);
|
||||
const tileSize = toSize(tileGrid.getTileSize(tileZ), this.tempSize_);
|
||||
const tileOrigin = tileGrid.getOrigin(tileZ);
|
||||
|
||||
const centerI =
|
||||
(centerX - tileOrigin[0]) / (tileSize[0] * tileResolution);
|
||||
const centerJ =
|
||||
(tileOrigin[1] - centerY) / (tileSize[1] * tileResolution);
|
||||
|
||||
const tileScale = viewState.resolution / tileResolution;
|
||||
|
||||
const depth = depthForZ(tileZ);
|
||||
const tileTextures = tileTexturesByZ[tileZ];
|
||||
for (let i = 0, ii = tileTextures.length; i < ii; ++i) {
|
||||
const tileTexture = tileTextures[i];
|
||||
if (!tileTexture.loaded) {
|
||||
continue;
|
||||
}
|
||||
const tile = tileTexture.tile;
|
||||
const tileCoord = tile.tileCoord;
|
||||
const tileCoordKey = getTileCoordKey(tileCoord);
|
||||
|
||||
const tileCenterI = tileCoord[1];
|
||||
const tileCenterJ = tileCoord[2];
|
||||
|
||||
composeTransform(
|
||||
this.tileTransform_,
|
||||
0,
|
||||
0,
|
||||
2 / ((frameState.size[0] * tileScale) / tileSize[0]),
|
||||
-2 / ((frameState.size[1] * tileScale) / tileSize[1]),
|
||||
viewState.rotation,
|
||||
-(centerI - tileCenterI),
|
||||
-(centerJ - tileCenterJ)
|
||||
);
|
||||
|
||||
this.helper.setUniformMatrixValue(
|
||||
Uniforms.TILE_TRANSFORM,
|
||||
mat4FromTransform(this.tempMat4_, this.tileTransform_)
|
||||
);
|
||||
|
||||
this.helper.bindBuffer(tileTexture.coords);
|
||||
this.helper.bindBuffer(this.indices_);
|
||||
this.helper.enableAttributes(attributeDescriptions);
|
||||
|
||||
for (
|
||||
let textureIndex = 0;
|
||||
textureIndex < tileTexture.textures.length;
|
||||
++textureIndex
|
||||
) {
|
||||
const textureProperty = 'TEXTURE' + textureIndex;
|
||||
const uniformName = Uniforms.TILE_TEXTURE_PREFIX + textureIndex;
|
||||
gl.activeTexture(gl[textureProperty]);
|
||||
gl.bindTexture(gl.TEXTURE_2D, tileTexture.textures[textureIndex]);
|
||||
gl.uniform1i(this.helper.getUniformLocation(uniformName), 0);
|
||||
}
|
||||
|
||||
const alpha =
|
||||
tileCoordKey in alphaLookup ? alphaLookup[tileCoordKey] : 1;
|
||||
|
||||
if (alpha < 1) {
|
||||
frameState.animate = true;
|
||||
}
|
||||
|
||||
this.helper.setUniformFloatValue(Uniforms.TRANSITION_ALPHA, alpha);
|
||||
this.helper.setUniformFloatValue(Uniforms.DEPTH, depth);
|
||||
this.helper.setUniformFloatValue(
|
||||
Uniforms.TEXTURE_PIXEL_WIDTH,
|
||||
tileSize[0]
|
||||
);
|
||||
this.helper.setUniformFloatValue(
|
||||
Uniforms.TEXTURE_PIXEL_HEIGHT,
|
||||
tileSize[1]
|
||||
);
|
||||
this.helper.setUniformFloatValue(
|
||||
Uniforms.RESOLUTION,
|
||||
viewState.resolution
|
||||
);
|
||||
this.helper.setUniformFloatValue(Uniforms.ZOOM, viewState.zoom);
|
||||
|
||||
this.helper.drawElements(0, this.indices_.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
this.helper.finalizeDraw(frameState);
|
||||
|
||||
const canvas = this.helper.getCanvas();
|
||||
|
||||
const opacity = layerState.opacity;
|
||||
if (this.renderedOpacity_ !== opacity) {
|
||||
canvas.style.opacity = String(opacity);
|
||||
this.renderedOpacity_ = opacity;
|
||||
}
|
||||
|
||||
while (tileTextureCache.canExpireCache()) {
|
||||
const tileTexture = tileTextureCache.pop();
|
||||
tileTexture.dispose();
|
||||
}
|
||||
|
||||
// TODO: let the renderers manage their own cache instead of managing the source cache
|
||||
/**
|
||||
* Here we unconditionally expire the source cache since the renderer maintains
|
||||
* its own cache.
|
||||
* @param {import("../../PluggableMap.js").default} map Map.
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
*/
|
||||
const postRenderFunction = function (map, frameState) {
|
||||
tileSource.expireCache(tileSource.getProjection(), empty);
|
||||
};
|
||||
|
||||
frameState.postRenderFunctions.push(postRenderFunction);
|
||||
|
||||
this.postRender(frameState);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for tiles covering the provided tile coordinate at an alternate
|
||||
* zoom level. Loaded tiles will be added to the provided tile texture lookup.
|
||||
* @param {import("../../tilegrid/TileGrid.js").default} tileGrid The tile grid.
|
||||
* @param {import("../../tilecoord.js").TileCoord} tileCoord The target tile coordinate.
|
||||
* @param {number} altZ The alternate zoom level.
|
||||
* @param {Object<string, Array<import("../../webgl/TileTexture.js").default>>} tileTexturesByZ Lookup of
|
||||
* tile textures by zoom level.
|
||||
* @return {boolean} The tile coordinate is covered by loaded tiles at the alternate zoom level.
|
||||
* @private
|
||||
*/
|
||||
findAltTiles_(tileGrid, tileCoord, altZ, tileTexturesByZ) {
|
||||
const tileRange = tileGrid.getTileRangeForTileCoordAndZ(
|
||||
tileCoord,
|
||||
altZ,
|
||||
this.tempTileRange_
|
||||
);
|
||||
|
||||
if (!tileRange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let covered = true;
|
||||
const tileTextureCache = this.tileTextureCache_;
|
||||
for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
||||
for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
||||
const cacheKey = getKeyZXY(altZ, x, y);
|
||||
let loaded = false;
|
||||
if (tileTextureCache.containsKey(cacheKey)) {
|
||||
const tileTexture = tileTextureCache.get(cacheKey);
|
||||
if (tileTexture.loaded) {
|
||||
addTileTextureToLookup(tileTexturesByZ, tileTexture, altZ);
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
if (!loaded) {
|
||||
covered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return covered;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @return {import("../../layer/WebGLTile.js").default}
|
||||
*/
|
||||
WebGLTileLayerRenderer.prototype.getLayer;
|
||||
|
||||
export default WebGLTileLayerRenderer;
|
||||
@@ -5,6 +5,8 @@
|
||||
export {default as BingMaps} from './source/BingMaps.js';
|
||||
export {default as CartoDB} from './source/CartoDB.js';
|
||||
export {default as Cluster} from './source/Cluster.js';
|
||||
export {default as DataTile} from './source/DataTile.js';
|
||||
export {default as GeoTIFF} from './source/GeoTIFF.js';
|
||||
export {default as IIIF} from './source/IIIF.js';
|
||||
export {default as Image} from './source/Image.js';
|
||||
export {default as ImageArcGISRest} from './source/ImageArcGISRest.js';
|
||||
|
||||
@@ -40,6 +40,18 @@ import {getUid} from '../util.js';
|
||||
* ```
|
||||
* See {@link module:ol/geom/Polygon~Polygon#getInteriorPoint} for a way to get a cluster
|
||||
* calculation point for polygons.
|
||||
* @property {function(Point, Array<Feature>):Feature} [createCluster]
|
||||
* Function that takes the cluster's center {@link module:ol/geom/Point} and an array
|
||||
* of {@link module:ol/Feature} included in this cluster. Must return a
|
||||
* {@link module:ol/Feature} that will be used to render. Default implementation is:
|
||||
* ```js
|
||||
* function(point, features) {
|
||||
* return new Feature({
|
||||
* geometry: point,
|
||||
* features: features
|
||||
* });
|
||||
* }
|
||||
* ```
|
||||
* @property {VectorSource} [source] Source.
|
||||
* @property {boolean} [wrapX=true] Whether to wrap the world horizontally.
|
||||
*/
|
||||
@@ -108,6 +120,12 @@ class Cluster extends VectorSource {
|
||||
return geometry;
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {function(Point, Array<Feature>):Feature}
|
||||
* @private
|
||||
*/
|
||||
this.createCustomCluster_ = options.createCluster;
|
||||
|
||||
/**
|
||||
* @type {VectorSource}
|
||||
* @protected
|
||||
@@ -294,9 +312,14 @@ class Cluster extends VectorSource {
|
||||
centroid[0] * (1 - ratio) + searchCenter[0] * ratio,
|
||||
centroid[1] * (1 - ratio) + searchCenter[1] * ratio,
|
||||
]);
|
||||
const cluster = new Feature(geometry);
|
||||
cluster.set('features', features, true);
|
||||
return cluster;
|
||||
if (this.createCustomCluster_) {
|
||||
return this.createCustomCluster_(geometry, features);
|
||||
} else {
|
||||
return new Feature({
|
||||
geometry,
|
||||
features,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
152
src/ol/source/DataTile.js
Normal file
152
src/ol/source/DataTile.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @module ol/source/DataTile
|
||||
*/
|
||||
import DataTile from '../DataTile.js';
|
||||
import EventType from '../events/EventType.js';
|
||||
import TileEventType from './TileEventType.js';
|
||||
import TileSource, {TileSourceEvent} from './Tile.js';
|
||||
import TileState from '../TileState.js';
|
||||
import {assign} from '../obj.js';
|
||||
import {createXYZ, extentFromProjection} from '../tilegrid.js';
|
||||
import {getKeyZXY} from '../tilecoord.js';
|
||||
import {getUid} from '../util.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Options
|
||||
* @property {function(number, number, number) : Promise<import("../DataTile.js").Data>} [loader] Data loader. Called with z, x, and y tile coordinates.
|
||||
* Returns a promise that resolves to a {@link import("../DataTile.js").Data}.
|
||||
* @property {number} [maxZoom=42] Optional max zoom level. Not used if `tileGrid` is provided.
|
||||
* @property {number} [minZoom=0] Optional min zoom level. Not used if `tileGrid` is provided.
|
||||
* @property {number|import("../size.js").Size} [tileSize=[256, 256]] The pixel width and height of the tiles.
|
||||
* @property {number} [maxResolution] Optional tile grid resolution at level zero. Not used if `tileGrid` is provided.
|
||||
* @property {import("../proj.js").ProjectionLike} [projection='EPSG:3857'] Tile projection.
|
||||
* @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid.
|
||||
* @property {boolean} [opaque=false] Whether the layer is opaque.
|
||||
* @property {import("./State.js").default} [state] The source state.
|
||||
* @property {number} [tilePixelRatio] Tile pixel ratio.
|
||||
* @property {boolean} [wrapX=true] Render tiles beyond the antimeridian.
|
||||
* @property {number} [transition] Transition time when fading in new tiles (in miliseconds).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Base class for sources providing tiles divided into a tile grid.
|
||||
*
|
||||
* @fires import("./Tile.js").TileSourceEvent
|
||||
* @api
|
||||
*/
|
||||
class DataTileSource extends TileSource {
|
||||
/**
|
||||
* @param {Options} options Image tile options.
|
||||
*/
|
||||
constructor(options) {
|
||||
const projection =
|
||||
options.projection === undefined ? 'EPSG:3857' : options.projection;
|
||||
|
||||
let tileGrid = options.tileGrid;
|
||||
if (tileGrid === undefined && projection) {
|
||||
tileGrid = createXYZ({
|
||||
extent: extentFromProjection(projection),
|
||||
maxResolution: options.maxResolution,
|
||||
maxZoom: options.maxZoom,
|
||||
minZoom: options.minZoom,
|
||||
tileSize: options.tileSize,
|
||||
});
|
||||
}
|
||||
|
||||
super({
|
||||
cacheSize: 0.1, // don't cache on the source
|
||||
projection: projection,
|
||||
tileGrid: tileGrid,
|
||||
opaque: options.opaque,
|
||||
state: options.state,
|
||||
tilePixelRatio: options.tilePixelRatio,
|
||||
wrapX: options.wrapX,
|
||||
transition: options.transition,
|
||||
});
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {!Object<string, boolean>}
|
||||
*/
|
||||
this.tileLoadingKeys_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this.loader_ = options.loader;
|
||||
|
||||
this.handleTileChange_ = this.handleTileChange_.bind(this);
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.bandCount = 4; // assume RGBA
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function(number, number, number) : Promise<import("../DataTile.js").Data>} loader The data loader.
|
||||
* @protected
|
||||
*/
|
||||
setLoader(loader) {
|
||||
this.loader_ = loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
* @param {number} z Tile coordinate z.
|
||||
* @param {number} x Tile coordinate x.
|
||||
* @param {number} y Tile coordinate y.
|
||||
* @param {number} pixelRatio Pixel ratio.
|
||||
* @param {import("../proj/Projection.js").default} projection Projection.
|
||||
* @return {!DataTile} Tile.
|
||||
*/
|
||||
getTile(z, x, y, pixelRatio, projection) {
|
||||
const tileCoordKey = getKeyZXY(z, x, y);
|
||||
if (this.tileCache.containsKey(tileCoordKey)) {
|
||||
return this.tileCache.get(tileCoordKey);
|
||||
}
|
||||
|
||||
const sourceLoader = this.loader_;
|
||||
function loader() {
|
||||
return sourceLoader(z, x, y);
|
||||
}
|
||||
|
||||
const tile = new DataTile(
|
||||
assign({tileCoord: [z, x, y], loader: loader}, this.tileOptions)
|
||||
);
|
||||
tile.key = this.getKey();
|
||||
tile.addEventListener(EventType.CHANGE, this.handleTileChange_);
|
||||
|
||||
this.tileCache.set(tileCoordKey, tile);
|
||||
return tile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tile change events.
|
||||
* @param {import("../events/Event.js").default} event Event.
|
||||
*/
|
||||
handleTileChange_(event) {
|
||||
const tile = /** @type {import("../Tile.js").default} */ (event.target);
|
||||
const uid = getUid(tile);
|
||||
const tileState = tile.getState();
|
||||
let type;
|
||||
if (tileState == TileState.LOADING) {
|
||||
this.tileLoadingKeys_[uid] = true;
|
||||
type = TileEventType.TILELOADSTART;
|
||||
} else if (uid in this.tileLoadingKeys_) {
|
||||
delete this.tileLoadingKeys_[uid];
|
||||
type =
|
||||
tileState == TileState.ERROR
|
||||
? TileEventType.TILELOADERROR
|
||||
: tileState == TileState.LOADED
|
||||
? TileEventType.TILELOADEND
|
||||
: undefined;
|
||||
}
|
||||
if (type) {
|
||||
this.dispatchEvent(new TileSourceEvent(type, tile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DataTileSource;
|
||||
652
src/ol/source/GeoTIFF.js
Normal file
652
src/ol/source/GeoTIFF.js
Normal file
@@ -0,0 +1,652 @@
|
||||
/**
|
||||
* @module ol/source/GeoTIFF
|
||||
*/
|
||||
import DataTile from './DataTile.js';
|
||||
import State from './State.js';
|
||||
import TileGrid from '../tilegrid/TileGrid.js';
|
||||
import {Pool, fromUrl as tiffFromUrl, fromUrls as tiffFromUrls} from 'geotiff';
|
||||
import {Projection, get as getCachedProjection} from '../proj.js';
|
||||
import {create as createDecoderWorker} from '../worker/geotiff-decoder.js';
|
||||
import {getIntersection} from '../extent.js';
|
||||
import {toSize} from '../size.js';
|
||||
import {fromCode as unitsFromCode} from '../proj/Units.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} SourceInfo
|
||||
* @property {string} url URL for the source GeoTIFF.
|
||||
* @property {Array<string>} [overviews] List of any overview URLs.
|
||||
* @property {number} [min=0] The minimum source data value. Rendered values are scaled from 0 to 1 based on
|
||||
* the configured min and max.
|
||||
* @property {number} [max] The maximum source data value. Rendered values are scaled from 0 to 1 based on
|
||||
* the configured min and max.
|
||||
* @property {number} [nodata] Values to discard. When provided, an additional band (alpha) will be added
|
||||
* to the data.
|
||||
* @property {Array<number>} [bands] Band numbers to be read from (where the first band is `1`). If not provided, all bands will
|
||||
* be read. For example, if a GeoTIFF has blue (1), green (2), red (3), and near-infrared (4) bands, and you only need the
|
||||
* near-infrared band, configure `bands: [4]`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GeoKeys
|
||||
* @property {number} GTModelTypeGeoKey Model type.
|
||||
* @property {number} GTRasterTypeGeoKey Raster type.
|
||||
* @property {number} GeogAngularUnitsGeoKey Angular units.
|
||||
* @property {number} GeogInvFlatteningGeoKey Inverse flattening.
|
||||
* @property {number} GeogSemiMajorAxisGeoKey Semi-major axis.
|
||||
* @property {number} GeographicTypeGeoKey Geographic coordinate system code.
|
||||
* @property {number} ProjLinearUnitsGeoKey Projected linear unit code.
|
||||
* @property {number} ProjectedCSTypeGeoKey Projected coordinate system code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GeoTIFFImage
|
||||
* @property {Object} fileDirectory The file directory.
|
||||
* @property {GeoKeys} geoKeys The parsed geo-keys.
|
||||
* @property {boolean} littleEndian Uses little endian byte order.
|
||||
* @property {Object} tiles The tile cache.
|
||||
* @property {boolean} isTiled The image is tiled.
|
||||
* @property {function():Array<number>} getBoundingBox Get the image bounding box.
|
||||
* @property {function():Array<number>} getOrigin Get the image origin.
|
||||
* @property {function(GeoTIFFImage):Array<number>} getResolution Get the image resolution.
|
||||
*/
|
||||
|
||||
let workerPool;
|
||||
function getWorkerPool() {
|
||||
if (!workerPool) {
|
||||
workerPool = new Pool(undefined, createDecoderWorker());
|
||||
}
|
||||
return workerPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bounding box of an image. If the image does not have an affine transform,
|
||||
* the pixel bounds are returned.
|
||||
* @param {GeoTIFFImage} image The image.
|
||||
* @return {Array<number>} The image bounding box.
|
||||
*/
|
||||
function getBoundingBox(image) {
|
||||
try {
|
||||
return image.getBoundingBox();
|
||||
} catch (_) {
|
||||
const fileDirectory = image.fileDirectory;
|
||||
return [0, 0, fileDirectory.ImageWidth, fileDirectory.ImageLength];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the origin of an image. If the image does not have an affine transform,
|
||||
* the top-left corner of the pixel bounds is returned.
|
||||
* @param {GeoTIFFImage} image The image.
|
||||
* @return {Array<number>} The image origin.
|
||||
*/
|
||||
function getOrigin(image) {
|
||||
try {
|
||||
return image.getOrigin().slice(0, 2);
|
||||
} catch (_) {
|
||||
return [0, image.fileDirectory.ImageLength];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resolution of an image. If the image does not have an affine transform,
|
||||
* the width of the image is compared with the reference image.
|
||||
* @param {GeoTIFFImage} image The image.
|
||||
* @param {GeoTIFFImage} referenceImage The reference image.
|
||||
* @return {number} The image resolution.
|
||||
*/
|
||||
function getResolution(image, referenceImage) {
|
||||
try {
|
||||
return image.getResolution(referenceImage)[0];
|
||||
} catch (_) {
|
||||
return (
|
||||
referenceImage.fileDirectory.ImageWidth / image.fileDirectory.ImageWidth
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GeoTIFFImage} image A GeoTIFF.
|
||||
* @return {import("../proj/Projection.js").default} The image projection.
|
||||
*/
|
||||
function getProjection(image) {
|
||||
const geoKeys = image.geoKeys;
|
||||
if (!geoKeys) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (geoKeys.ProjectedCSTypeGeoKey) {
|
||||
const code = 'EPSG:' + geoKeys.ProjectedCSTypeGeoKey;
|
||||
let projection = getCachedProjection(code);
|
||||
if (!projection) {
|
||||
const units = unitsFromCode(geoKeys.ProjLinearUnitsGeoKey);
|
||||
if (units) {
|
||||
projection = new Projection({
|
||||
code: code,
|
||||
units: units,
|
||||
});
|
||||
}
|
||||
}
|
||||
return projection;
|
||||
}
|
||||
|
||||
if (geoKeys.GeographicTypeGeoKey) {
|
||||
const code = 'EPSG:' + geoKeys.GeographicTypeGeoKey;
|
||||
let projection = getCachedProjection(code);
|
||||
if (!projection) {
|
||||
const units = unitsFromCode(geoKeys.GeogAngularUnitsGeoKey);
|
||||
if (units) {
|
||||
projection = new Projection({
|
||||
code: code,
|
||||
units: units,
|
||||
});
|
||||
}
|
||||
}
|
||||
return projection;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("geotiff/src/geotiff.js").GeoTIFF|import("geotiff/src/geotiff.js").MultiGeoTIFF} tiff A GeoTIFF.
|
||||
* @return {Promise<Array<import("geotiff/src/geotiffimage.js").GeoTIFFImage>>} Resolves to a list of images.
|
||||
*/
|
||||
function getImagesForTIFF(tiff) {
|
||||
return tiff.getImageCount().then(function (count) {
|
||||
const requests = new Array(count);
|
||||
for (let i = 0; i < count; ++i) {
|
||||
requests[i] = tiff.getImage(i);
|
||||
}
|
||||
return Promise.all(requests);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SourceInfo} source The GeoTIFF source.
|
||||
* @return {Promise<Array<import("geotiff/src/geotiffimage.js").GeoTIFFImage>>} Resolves to a list of images.
|
||||
*/
|
||||
function getImagesForSource(source) {
|
||||
let request;
|
||||
if (source.overviews) {
|
||||
request = tiffFromUrls(source.url, source.overviews);
|
||||
} else {
|
||||
request = tiffFromUrl(source.url);
|
||||
}
|
||||
return request.then(getImagesForTIFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number|Array<number>|Array<Array<number>>} expected Expected value.
|
||||
* @param {number|Array<number>|Array<Array<number>>} got Actual value.
|
||||
* @param {number} tolerance Accepted tolerance in fraction of expected between expected and got.
|
||||
* @param {string} message The error message.
|
||||
*/
|
||||
function assertEqual(expected, got, tolerance, message) {
|
||||
if (Array.isArray(expected)) {
|
||||
const length = expected.length;
|
||||
if (!Array.isArray(got) || length != got.length) {
|
||||
throw new Error(message);
|
||||
}
|
||||
for (let i = 0; i < length; ++i) {
|
||||
assertEqual(expected[i], got[i], tolerance, message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
got = /** @type {number} */ (got);
|
||||
if (Math.abs(expected - got) > tolerance * expected) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} array The data array.
|
||||
* @return {number} The minimum value.
|
||||
*/
|
||||
function getMinForDataType(array) {
|
||||
if (array instanceof Int8Array) {
|
||||
return -128;
|
||||
}
|
||||
if (array instanceof Int16Array) {
|
||||
return -32768;
|
||||
}
|
||||
if (array instanceof Int32Array) {
|
||||
return -2147483648;
|
||||
}
|
||||
if (array instanceof Float32Array) {
|
||||
return 1.2e-38;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} array The data array.
|
||||
* @return {number} The maximum value.
|
||||
*/
|
||||
function getMaxForDataType(array) {
|
||||
if (array instanceof Int8Array) {
|
||||
return 127;
|
||||
}
|
||||
if (array instanceof Uint8Array) {
|
||||
return 255;
|
||||
}
|
||||
if (array instanceof Uint8ClampedArray) {
|
||||
return 255;
|
||||
}
|
||||
if (array instanceof Int16Array) {
|
||||
return 32767;
|
||||
}
|
||||
if (array instanceof Uint16Array) {
|
||||
return 65535;
|
||||
}
|
||||
if (array instanceof Int32Array) {
|
||||
return 2147483647;
|
||||
}
|
||||
if (array instanceof Uint32Array) {
|
||||
return 4294967295;
|
||||
}
|
||||
if (array instanceof Float32Array) {
|
||||
return 3.4e38;
|
||||
}
|
||||
return 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} Options
|
||||
* @property {Array<SourceInfo>} sources List of information about GeoTIFF sources.
|
||||
* Multiple sources can be combined when their resolution sets are equal after applying a scale.
|
||||
* The list of sources defines a mapping between input bands as they are read from each GeoTIFF and
|
||||
* the output bands that are provided by data tiles. To control which bands to read from each GeoTIFF,
|
||||
* use the {@link import("./GeoTIFF.js").SourceInfo bands} property. If, for example, you specify two
|
||||
* sources, one with 3 bands and {@link import("./GeoTIFF.js").SourceInfo nodata} configured, and
|
||||
* another with 1 band, the resulting data tiles will have 5 bands: 3 from the first source, 1 alpha
|
||||
* band from the first source, and 1 band from the second source.
|
||||
* @property {boolean} [convertToRGB = false] By default, bands from the sources are read as-is. When
|
||||
* reading GeoTIFFs with the purpose of displaying them as RGB images, setting this to `true` will
|
||||
* convert other color spaces (YCbCr, CMYK) to RGB.
|
||||
* @property {boolean} [opaque=false] Whether the layer is opaque.
|
||||
* @property {number} [transition=250] Duration of the opacity transition for rendering.
|
||||
* To disable the opacity transition, pass `transition: 0`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* A source for working with GeoTIFF data.
|
||||
* @api
|
||||
*/
|
||||
class GeoTIFFSource extends DataTile {
|
||||
/**
|
||||
* @param {Options} options Data tile options.
|
||||
*/
|
||||
constructor(options) {
|
||||
super({
|
||||
state: State.LOADING,
|
||||
tileGrid: null,
|
||||
projection: null,
|
||||
opaque: options.opaque,
|
||||
transition: options.transition,
|
||||
});
|
||||
|
||||
/**
|
||||
* @type {Array<SourceInfo>}
|
||||
* @private
|
||||
*/
|
||||
this.sourceInfo_ = options.sources;
|
||||
|
||||
const numSources = this.sourceInfo_.length;
|
||||
|
||||
/**
|
||||
* @type {Array<Array<import("geotiff/src/geotiffimage.js").GeoTIFFImage>>}
|
||||
* @private
|
||||
*/
|
||||
this.sourceImagery_ = new Array(numSources);
|
||||
|
||||
/**
|
||||
* @type {Array<number>}
|
||||
* @private
|
||||
*/
|
||||
this.resolutionFactors_ = new Array(numSources);
|
||||
|
||||
/**
|
||||
* @type {Array<number>}
|
||||
* @private
|
||||
*/
|
||||
this.samplesPerPixel_;
|
||||
|
||||
/**
|
||||
* @type {Array<Array<number>>}
|
||||
* @private
|
||||
*/
|
||||
this.nodataValues_;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.addAlpha_ = false;
|
||||
|
||||
/**
|
||||
* @type {Error}
|
||||
* @private
|
||||
*/
|
||||
this.error_ = null;
|
||||
|
||||
/**
|
||||
* @type {'readRasters' | 'readRGB'}
|
||||
*/
|
||||
this.readMethod_ = options.convertToRGB ? 'readRGB' : 'readRasters';
|
||||
|
||||
this.setKey(this.sourceInfo_.map((source) => source.url).join(','));
|
||||
|
||||
const self = this;
|
||||
const requests = new Array(numSources);
|
||||
for (let i = 0; i < numSources; ++i) {
|
||||
requests[i] = getImagesForSource(this.sourceInfo_[i]);
|
||||
}
|
||||
Promise.all(requests)
|
||||
.then(function (sources) {
|
||||
self.configure_(sources);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error(error); // eslint-disable-line no-console
|
||||
self.error_ = error;
|
||||
self.setState(State.ERROR);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Error} A source loading error. When the source state is `error`, use this function
|
||||
* to get more information about the error. To debug a faulty configuration, you may want to use
|
||||
* a listener like
|
||||
* ```js
|
||||
* geotiffSource.on('change', () => {
|
||||
* if (geotiffSource.getState() === 'error') {
|
||||
* console.error(geotiffSource.getError());
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
getError() {
|
||||
return this.error_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the tile grid based on images within the source GeoTIFFs. Each GeoTIFF
|
||||
* must have the same internal tiled structure.
|
||||
* @param {Array<Array<import("geotiff/src/geotiffimage.js").GeoTIFFImage>>} sources Each source is a list of images
|
||||
* from a single GeoTIFF.
|
||||
* @private
|
||||
*/
|
||||
configure_(sources) {
|
||||
let extent;
|
||||
let origin;
|
||||
let tileSizes;
|
||||
let resolutions;
|
||||
const samplesPerPixel = new Array(sources.length);
|
||||
const nodataValues = new Array(sources.length);
|
||||
let minZoom = 0;
|
||||
|
||||
const sourceCount = sources.length;
|
||||
for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
|
||||
const images = sources[sourceIndex];
|
||||
const imageCount = images.length;
|
||||
|
||||
let sourceExtent;
|
||||
let sourceOrigin;
|
||||
const sourceTileSizes = new Array(imageCount);
|
||||
const sourceResolutions = new Array(imageCount);
|
||||
|
||||
nodataValues[sourceIndex] = new Array(imageCount);
|
||||
|
||||
for (let imageIndex = 0; imageIndex < imageCount; ++imageIndex) {
|
||||
const image = images[imageIndex];
|
||||
const nodataValue = image.getGDALNoData();
|
||||
nodataValues[sourceIndex][imageIndex] =
|
||||
nodataValue === null ? NaN : nodataValue;
|
||||
|
||||
const wantedSamples = this.sourceInfo_[sourceIndex].bands;
|
||||
samplesPerPixel[sourceIndex] = wantedSamples
|
||||
? wantedSamples.length
|
||||
: image.getSamplesPerPixel();
|
||||
const level = imageCount - (imageIndex + 1);
|
||||
|
||||
if (!sourceExtent) {
|
||||
sourceExtent = getBoundingBox(image);
|
||||
}
|
||||
|
||||
if (!sourceOrigin) {
|
||||
sourceOrigin = getOrigin(image);
|
||||
}
|
||||
|
||||
sourceResolutions[level] = getResolution(image, images[0]);
|
||||
sourceTileSizes[level] = [image.getTileWidth(), image.getTileHeight()];
|
||||
}
|
||||
|
||||
if (!extent) {
|
||||
extent = sourceExtent;
|
||||
} else {
|
||||
getIntersection(extent, sourceExtent, extent);
|
||||
}
|
||||
|
||||
if (!origin) {
|
||||
origin = sourceOrigin;
|
||||
} else {
|
||||
const message = `Origin mismatch for source ${sourceIndex}, got [${sourceOrigin}] but expected [${origin}]`;
|
||||
assertEqual(origin, sourceOrigin, 0, message);
|
||||
}
|
||||
|
||||
if (!resolutions) {
|
||||
resolutions = sourceResolutions;
|
||||
this.resolutionFactors_[sourceIndex] = 1;
|
||||
} else {
|
||||
if (resolutions.length - minZoom > sourceResolutions.length) {
|
||||
minZoom = resolutions.length - sourceResolutions.length;
|
||||
}
|
||||
const resolutionFactor =
|
||||
resolutions[resolutions.length - 1] /
|
||||
sourceResolutions[sourceResolutions.length - 1];
|
||||
this.resolutionFactors_[sourceIndex] = resolutionFactor;
|
||||
const scaledSourceResolutions = sourceResolutions.map(
|
||||
(resolution) => (resolution *= resolutionFactor)
|
||||
);
|
||||
const message = `Resolution mismatch for source ${sourceIndex}, got [${scaledSourceResolutions}] but expected [${resolutions}]`;
|
||||
assertEqual(
|
||||
resolutions.slice(minZoom, resolutions.length),
|
||||
scaledSourceResolutions,
|
||||
0.005,
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
if (!tileSizes) {
|
||||
tileSizes = sourceTileSizes;
|
||||
} else {
|
||||
assertEqual(
|
||||
tileSizes.slice(minZoom, tileSizes.length),
|
||||
sourceTileSizes,
|
||||
0,
|
||||
`Tile size mismatch for source ${sourceIndex}`
|
||||
);
|
||||
}
|
||||
|
||||
this.sourceImagery_[sourceIndex] = images.reverse();
|
||||
}
|
||||
|
||||
for (let i = 0, ii = this.sourceImagery_.length; i < ii; ++i) {
|
||||
const sourceImagery = this.sourceImagery_[i];
|
||||
while (sourceImagery.length < resolutions.length) {
|
||||
sourceImagery.unshift(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.getProjection()) {
|
||||
const firstSource = sources[0];
|
||||
for (let i = firstSource.length - 1; i >= 0; --i) {
|
||||
const image = firstSource[i];
|
||||
const projection = getProjection(image);
|
||||
if (projection) {
|
||||
this.projection = projection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.samplesPerPixel_ = samplesPerPixel;
|
||||
this.nodataValues_ = nodataValues;
|
||||
|
||||
// decide if we need to add an alpha band to handle nodata
|
||||
outer: for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
|
||||
// option 1: source is configured with a nodata value
|
||||
if (this.sourceInfo_[sourceIndex].nodata !== undefined) {
|
||||
this.addAlpha_ = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const values = nodataValues[sourceIndex];
|
||||
|
||||
// option 2: check image metadata for limited bands
|
||||
const bands = this.sourceInfo_[sourceIndex].bands;
|
||||
if (bands) {
|
||||
for (let i = 0; i < bands.length; ++i) {
|
||||
if (!isNaN(values[bands[i] - 1])) {
|
||||
this.addAlpha_ = true;
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// option 3: check image metadata for all bands
|
||||
for (let imageIndex = 0; imageIndex < values.length; ++imageIndex) {
|
||||
if (!isNaN(values[imageIndex])) {
|
||||
this.addAlpha_ = true;
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const additionalBands = this.addAlpha_ ? 1 : 0;
|
||||
this.bandCount =
|
||||
samplesPerPixel.reduce((accumulator, value) => {
|
||||
accumulator += value;
|
||||
return accumulator;
|
||||
}, 0) + additionalBands;
|
||||
|
||||
const tileGrid = new TileGrid({
|
||||
extent: extent,
|
||||
minZoom: minZoom,
|
||||
origin: origin,
|
||||
resolutions: resolutions,
|
||||
tileSizes: tileSizes,
|
||||
});
|
||||
|
||||
this.tileGrid = tileGrid;
|
||||
|
||||
this.setLoader(this.loadTile_.bind(this));
|
||||
this.setState(State.READY);
|
||||
}
|
||||
|
||||
loadTile_(z, x, y) {
|
||||
const size = toSize(this.tileGrid.getTileSize(z));
|
||||
|
||||
const sourceCount = this.sourceImagery_.length;
|
||||
const requests = new Array(sourceCount);
|
||||
const addAlpha = this.addAlpha_;
|
||||
const bandCount = this.bandCount;
|
||||
const samplesPerPixel = this.samplesPerPixel_;
|
||||
const sourceInfo = this.sourceInfo_;
|
||||
for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
|
||||
const source = sourceInfo[sourceIndex];
|
||||
const resolutionFactor = this.resolutionFactors_[sourceIndex];
|
||||
const pixelBounds = [
|
||||
Math.round(x * (size[0] * resolutionFactor)),
|
||||
Math.round(y * (size[1] * resolutionFactor)),
|
||||
Math.round((x + 1) * (size[0] * resolutionFactor)),
|
||||
Math.round((y + 1) * (size[1] * resolutionFactor)),
|
||||
];
|
||||
const image = this.sourceImagery_[sourceIndex][z];
|
||||
let samples;
|
||||
if (source.bands) {
|
||||
samples = source.bands.map(function (bandNumber) {
|
||||
return bandNumber - 1;
|
||||
});
|
||||
}
|
||||
requests[sourceIndex] = image[this.readMethod_]({
|
||||
window: pixelBounds,
|
||||
width: size[0],
|
||||
height: size[1],
|
||||
samples: samples,
|
||||
fillValue: source.nodata,
|
||||
pool: getWorkerPool(),
|
||||
interleave: false,
|
||||
});
|
||||
}
|
||||
|
||||
const pixelCount = size[0] * size[1];
|
||||
const dataLength = pixelCount * bandCount;
|
||||
const nodataValues = this.nodataValues_;
|
||||
|
||||
return Promise.all(requests).then(function (sourceSamples) {
|
||||
const data = new Uint8ClampedArray(dataLength);
|
||||
let dataIndex = 0;
|
||||
for (let pixelIndex = 0; pixelIndex < pixelCount; ++pixelIndex) {
|
||||
let transparent = addAlpha;
|
||||
for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
|
||||
const source = sourceInfo[sourceIndex];
|
||||
let min = source.min;
|
||||
if (min === undefined) {
|
||||
min = getMinForDataType(sourceSamples[sourceIndex][0]);
|
||||
}
|
||||
let max = source.max;
|
||||
if (max === undefined) {
|
||||
max = getMaxForDataType(sourceSamples[sourceIndex][0]);
|
||||
}
|
||||
|
||||
const gain = 255 / (max - min);
|
||||
const bias = -min * gain;
|
||||
|
||||
for (
|
||||
let sampleIndex = 0;
|
||||
sampleIndex < samplesPerPixel[sourceIndex];
|
||||
++sampleIndex
|
||||
) {
|
||||
const sourceValue =
|
||||
sourceSamples[sourceIndex][sampleIndex][pixelIndex];
|
||||
|
||||
const value = gain * sourceValue + bias;
|
||||
if (!addAlpha) {
|
||||
data[dataIndex] = value;
|
||||
} else {
|
||||
let nodata = source.nodata;
|
||||
if (nodata === undefined) {
|
||||
let bandIndex;
|
||||
if (source.bands) {
|
||||
bandIndex = source.bands[sampleIndex] - 1;
|
||||
} else {
|
||||
bandIndex = sampleIndex;
|
||||
}
|
||||
nodata = nodataValues[sourceIndex][bandIndex];
|
||||
}
|
||||
|
||||
if (sourceValue !== nodata) {
|
||||
transparent = false;
|
||||
data[dataIndex] = value;
|
||||
}
|
||||
}
|
||||
dataIndex++;
|
||||
}
|
||||
}
|
||||
if (addAlpha) {
|
||||
if (!transparent) {
|
||||
data[dataIndex] = 255;
|
||||
}
|
||||
dataIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default GeoTIFFSource;
|
||||
93
src/ol/source/OGCMapTile.js
Normal file
93
src/ol/source/OGCMapTile.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @module ol/source/OGCMapTile
|
||||
*/
|
||||
import SourceState from './State.js';
|
||||
import TileImage from './TileImage.js';
|
||||
import {getTileSetInfo} from './ogcTileUtil.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Options
|
||||
* @property {string} url URL to the OGC Map Tileset endpoint.
|
||||
* @property {Object} [context] A lookup of values to use in the tile URL template. The `{tileMatrix}`
|
||||
* (zoom level), `{tileRow}`, and `{tileCol}` variables in the URL will always be provided by the source.
|
||||
* @property {string} [mediaType] The content type for the tiles (e.g. "image/png"). If not provided,
|
||||
* the source will try to find a link with rel="item" that uses a supported image type.
|
||||
* @property {import("../proj.js").ProjectionLike} [projection] Projection. By default, the projection
|
||||
* will be derived from the `crs` of the `tileMatrixSet`. You can override this by supplying
|
||||
* a projection to the constructor.
|
||||
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
|
||||
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
|
||||
* @property {null|string} [crossOrigin] 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.
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
|
||||
* @property {boolean} [imageSmoothing=true] Enable image smoothing.
|
||||
* @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels).
|
||||
* Higher values can increase reprojection performance, but decrease precision.
|
||||
* @property {import("../Tile.js").LoadFunction} [tileLoadFunction] Optional function to load a tile given a URL. The default is
|
||||
* ```js
|
||||
* function(tile, src) {
|
||||
* tile.getImage().src = src;
|
||||
* };
|
||||
* ```
|
||||
* @property {boolean} [wrapX=true] Whether to wrap the world horizontally.
|
||||
* @property {number} [transition] Duration of the opacity transition for rendering.
|
||||
* To disable the opacity transition, pass `transition: 0`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Layer source for map tiles from an [OGC API - Tiles](https://ogcapi.ogc.org/tiles/) service that provides "map" type tiles.
|
||||
* The service must conform to at least the core (http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/core)
|
||||
* and tileset (http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/tileset) conformance classes.
|
||||
*/
|
||||
class OGCMapTile extends TileImage {
|
||||
/**
|
||||
* @param {Options} options OGC map tile options.
|
||||
*/
|
||||
constructor(options) {
|
||||
super({
|
||||
attributions: options.attributions,
|
||||
cacheSize: options.cacheSize,
|
||||
crossOrigin: options.crossOrigin,
|
||||
imageSmoothing: options.imageSmoothing,
|
||||
projection: options.projection,
|
||||
reprojectionErrorThreshold: options.reprojectionErrorThreshold,
|
||||
state: SourceState.LOADING,
|
||||
tileLoadFunction: options.tileLoadFunction,
|
||||
wrapX: options.wrapX !== undefined ? options.wrapX : true,
|
||||
transition: options.transition,
|
||||
});
|
||||
|
||||
const sourceInfo = {
|
||||
url: options.url,
|
||||
projection: this.getProjection(),
|
||||
mediaType: options.mediaType,
|
||||
context: options.context || null,
|
||||
};
|
||||
|
||||
getTileSetInfo(sourceInfo)
|
||||
.then(this.handleTileSetInfo_.bind(this))
|
||||
.catch(this.handleError_.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./ogcTileUtil.js").TileSetInfo} tileSetInfo Tile set info.
|
||||
* @private
|
||||
*/
|
||||
handleTileSetInfo_(tileSetInfo) {
|
||||
this.tileGrid = tileSetInfo.grid;
|
||||
this.setTileUrlFunction(tileSetInfo.urlFunction, tileSetInfo.urlTemplate);
|
||||
this.setState(SourceState.READY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Error} error The error.
|
||||
*/
|
||||
handleError_(error) {
|
||||
console.error(error); // eslint-disable-line no-console
|
||||
this.setState(SourceState.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
export default OGCMapTile;
|
||||
100
src/ol/source/OGCVectorTile.js
Normal file
100
src/ol/source/OGCVectorTile.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* @module ol/source/OGCVectorTile
|
||||
*/
|
||||
|
||||
import SourceState from './State.js';
|
||||
import VectorTile from './VectorTile.js';
|
||||
import {getTileSetInfo} from './ogcTileUtil.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Options
|
||||
* @property {string} url URL to the OGC Vector Tileset endpoint.
|
||||
* @property {Object} [context] A lookup of values to use in the tile URL template. The `{tileMatrix}`
|
||||
* (zoom level), `{tileRow}`, and `{tileCol}` variables in the URL will always be provided by the source.
|
||||
* @property {import("../format/Feature.js").default} format Feature parser for tiles.
|
||||
* @property {string} [mediaType] The content type for the tiles (e.g. "application/vnd.mapbox-vector-tile"). If not provided,
|
||||
* the source will try to find a link with rel="item" that uses a vector type supported by the configured format.
|
||||
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
|
||||
* @property {boolean} [attributionsCollapsible=true] Attributions are collapsible.
|
||||
* @property {number} [cacheSize] Initial tile cache size. Will auto-grow to hold at least twice the number of tiles in the viewport.
|
||||
* @property {boolean} [overlaps=true] This source may have overlapping geometries. Setting this
|
||||
* to `false` (e.g. for sources with polygons that represent administrative
|
||||
* boundaries or TopoJSON sources) allows the renderer to optimise fill and
|
||||
* stroke operations.
|
||||
* @property {import("../proj.js").ProjectionLike} [projection='EPSG:3857'] Projection of the tile grid.
|
||||
* @property {typeof import("../VectorTile.js").default} [tileClass] Class used to instantiate image tiles.
|
||||
* Default is {@link module:ol/VectorTile}.
|
||||
* @property {number} [transition] A duration for tile opacity
|
||||
* transitions in milliseconds. A duration of 0 disables the opacity transition.
|
||||
* @property {boolean} [wrapX=true] Whether to wrap the world horizontally.
|
||||
* When set to `false`, only one world
|
||||
* will be rendered. When set to `true`, tiles will be wrapped horizontally to
|
||||
* render multiple worlds.
|
||||
* @property {number|import("../array.js").NearestDirectionFunction} [zDirection=1]
|
||||
* Choose whether to use tiles with a higher or lower zoom level when between integer
|
||||
* zoom levels. See {@link module:ol/tilegrid/TileGrid~TileGrid#getZForResolution}.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Layer source for map tiles from an [OGC API - Tiles](https://ogcapi.ogc.org/tiles/) service that provides "vector" type tiles.
|
||||
* The service must conform to at least the core (http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/core)
|
||||
* and tileset (http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/tileset) conformance classes.
|
||||
*
|
||||
* Vector tile sets may come in a variety of formats (e.g. GeoJSON, MVT). The `format` option is used to determine
|
||||
* which of the advertised media types is used. If you need to force the use of a particular media type, you can
|
||||
* provide the `mediaType` option.
|
||||
*/
|
||||
class OGCVectorTile extends VectorTile {
|
||||
/**
|
||||
* @param {Options} options OGC vector tile options.
|
||||
*/
|
||||
constructor(options) {
|
||||
super({
|
||||
attributions: options.attributions,
|
||||
attributionsCollapsible: options.attributionsCollapsible,
|
||||
cacheSize: options.cacheSize,
|
||||
format: options.format,
|
||||
overlaps: options.overlaps,
|
||||
projection: options.projection,
|
||||
tileClass: options.tileClass,
|
||||
transition: options.transition,
|
||||
wrapX: options.wrapX,
|
||||
zDirection: options.zDirection,
|
||||
state: SourceState.LOADING,
|
||||
});
|
||||
|
||||
const sourceInfo = {
|
||||
url: options.url,
|
||||
projection: this.getProjection(),
|
||||
mediaType: options.mediaType,
|
||||
supportedMediaTypes: options.format.supportedMediaTypes,
|
||||
context: options.context || null,
|
||||
};
|
||||
|
||||
getTileSetInfo(sourceInfo)
|
||||
.then(this.handleTileSetInfo_.bind(this))
|
||||
.catch(this.handleError_.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./ogcTileUtil.js").TileSetInfo} tileSetInfo Tile set info.
|
||||
* @private
|
||||
*/
|
||||
handleTileSetInfo_(tileSetInfo) {
|
||||
this.tileGrid = tileSetInfo.grid;
|
||||
this.setTileUrlFunction(tileSetInfo.urlFunction, tileSetInfo.urlTemplate);
|
||||
this.setState(SourceState.READY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Error} error The error.
|
||||
*/
|
||||
handleError_(error) {
|
||||
console.error(error); // eslint-disable-line no-console
|
||||
this.setState(SourceState.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
export default OGCVectorTile;
|
||||
@@ -24,7 +24,8 @@ try {
|
||||
hasImageData = false;
|
||||
}
|
||||
|
||||
const context = document.createElement('canvas').getContext('2d');
|
||||
/** @type {CanvasRenderingContext2D} */
|
||||
let context;
|
||||
|
||||
/**
|
||||
* @param {Uint8ClampedArray} data Image data.
|
||||
@@ -35,19 +36,31 @@ const context = document.createElement('canvas').getContext('2d');
|
||||
export function newImageData(data, width, height) {
|
||||
if (hasImageData) {
|
||||
return new ImageData(data, width, height);
|
||||
} else {
|
||||
const imageData = context.createImageData(width, height);
|
||||
imageData.data.set(data);
|
||||
return imageData;
|
||||
}
|
||||
|
||||
if (!context) {
|
||||
context = document.createElement('canvas').getContext('2d');
|
||||
}
|
||||
const imageData = context.createImageData(width, height);
|
||||
imageData.data.set(data);
|
||||
return imageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} MinionData
|
||||
* @property {Array<ArrayBuffer>} buffers Array of buffers.
|
||||
* @property {Object} meta Operation metadata.
|
||||
* @property {boolean} imageOps The operation is an image operation.
|
||||
* @property {number} width The width of the image.
|
||||
* @property {number} height The height of the image.
|
||||
*/
|
||||
|
||||
/* istanbul ignore next */
|
||||
/**
|
||||
* Create a function for running operations. This function is serialized for
|
||||
* use in a worker.
|
||||
* @param {function(Array, Object):*} operation The operation.
|
||||
* @return {function(Object):ArrayBuffer} A function that takes an object with
|
||||
* @return {function(MinionData):ArrayBuffer} A function that takes an object with
|
||||
* buffers, meta, imageOps, width, and height properties and returns an array
|
||||
* buffer.
|
||||
*/
|
||||
@@ -77,40 +90,40 @@ function createMinion(operation) {
|
||||
|
||||
const numBuffers = buffers.length;
|
||||
const numBytes = buffers[0].byteLength;
|
||||
let output, b;
|
||||
|
||||
if (imageOps) {
|
||||
const images = new Array(numBuffers);
|
||||
for (b = 0; b < numBuffers; ++b) {
|
||||
for (let b = 0; b < numBuffers; ++b) {
|
||||
images[b] = newWorkerImageData(
|
||||
new Uint8ClampedArray(buffers[b]),
|
||||
width,
|
||||
height
|
||||
);
|
||||
}
|
||||
output = operation(images, meta).data;
|
||||
} else {
|
||||
output = new Uint8ClampedArray(numBytes);
|
||||
const arrays = new Array(numBuffers);
|
||||
const pixels = new Array(numBuffers);
|
||||
for (b = 0; b < numBuffers; ++b) {
|
||||
arrays[b] = new Uint8ClampedArray(buffers[b]);
|
||||
pixels[b] = [0, 0, 0, 0];
|
||||
}
|
||||
for (let i = 0; i < numBytes; i += 4) {
|
||||
for (let j = 0; j < numBuffers; ++j) {
|
||||
const array = arrays[j];
|
||||
pixels[j][0] = array[i];
|
||||
pixels[j][1] = array[i + 1];
|
||||
pixels[j][2] = array[i + 2];
|
||||
pixels[j][3] = array[i + 3];
|
||||
}
|
||||
const pixel = operation(pixels, meta);
|
||||
output[i] = pixel[0];
|
||||
output[i + 1] = pixel[1];
|
||||
output[i + 2] = pixel[2];
|
||||
output[i + 3] = pixel[3];
|
||||
const output = operation(images, meta).data;
|
||||
return output.buffer;
|
||||
}
|
||||
|
||||
const output = new Uint8ClampedArray(numBytes);
|
||||
const arrays = new Array(numBuffers);
|
||||
const pixels = new Array(numBuffers);
|
||||
for (let b = 0; b < numBuffers; ++b) {
|
||||
arrays[b] = new Uint8ClampedArray(buffers[b]);
|
||||
pixels[b] = [0, 0, 0, 0];
|
||||
}
|
||||
for (let i = 0; i < numBytes; i += 4) {
|
||||
for (let j = 0; j < numBuffers; ++j) {
|
||||
const array = arrays[j];
|
||||
pixels[j][0] = array[i];
|
||||
pixels[j][1] = array[i + 1];
|
||||
pixels[j][2] = array[i + 2];
|
||||
pixels[j][3] = array[i + 3];
|
||||
}
|
||||
const pixel = operation(pixels, meta);
|
||||
output[i] = pixel[0];
|
||||
output[i + 1] = pixel[1];
|
||||
output[i + 2] = pixel[2];
|
||||
output[i + 3] = pixel[3];
|
||||
}
|
||||
return output.buffer;
|
||||
};
|
||||
@@ -118,7 +131,7 @@ function createMinion(operation) {
|
||||
|
||||
/**
|
||||
* Create a worker for running operations.
|
||||
* @param {Object} config Configuration.
|
||||
* @param {ProcessorOptions} config Processor options.
|
||||
* @param {function(MessageEvent): void} onMessage Called with a message event.
|
||||
* @return {Worker} The worker.
|
||||
*/
|
||||
@@ -173,11 +186,22 @@ function createFauxWorker(config, onMessage) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {function(Error, ImageData, (Object|Array<Object>)): void} JobCallback
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Job
|
||||
* @property {Object} meta Job metadata.
|
||||
* @property {Array<ImageData>} inputs Array of input data.
|
||||
* @property {JobCallback} callback Called when the job is complete.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ProcessorOptions
|
||||
* @property {number} threads Number of workers to spawn.
|
||||
* @property {function(Array, Object):*} operation The operation.
|
||||
* @property {Object} [lib] Functions that will be made available to operations run in a worker.
|
||||
* @property {Operation} operation The operation.
|
||||
* @property {Object<string, Function>} [lib] Functions that will be made available to operations run in a worker.
|
||||
* @property {number} queue The number of queued jobs to allow.
|
||||
* @property {boolean} [imageOps=false] Pass all the image data to the operation instead of a single pixel.
|
||||
*/
|
||||
@@ -202,7 +226,11 @@ export class Processor extends Disposable {
|
||||
} else {
|
||||
threads = config.threads || 1;
|
||||
}
|
||||
const workers = [];
|
||||
|
||||
/**
|
||||
* @type {Array<Worker>}
|
||||
*/
|
||||
const workers = new Array(threads);
|
||||
if (threads) {
|
||||
for (let i = 0; i < threads; ++i) {
|
||||
workers[i] = createWorker(config, this._onWorkerMessage.bind(this, i));
|
||||
@@ -214,17 +242,32 @@ export class Processor extends Disposable {
|
||||
);
|
||||
}
|
||||
this._workers = workers;
|
||||
|
||||
/**
|
||||
* @type {Array<Job>}
|
||||
* @private
|
||||
*/
|
||||
this._queue = [];
|
||||
|
||||
this._maxQueueLength = config.queue || Infinity;
|
||||
this._running = 0;
|
||||
|
||||
/**
|
||||
* @type {Object<number, any>}
|
||||
* @private
|
||||
*/
|
||||
this._dataLookup = {};
|
||||
|
||||
/**
|
||||
* @type {Job}
|
||||
* @private
|
||||
*/
|
||||
this._job = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run operation on input data.
|
||||
* @param {Array<Array|ImageData>} inputs Array of pixels or image data
|
||||
* (depending on the operation type).
|
||||
* @param {Array<ImageData>} inputs Array of image data.
|
||||
* @param {Object} meta A user data object. This is passed to all operations
|
||||
* and must be serializable.
|
||||
* @param {function(Error, ImageData, Object): void} callback Called when work
|
||||
@@ -242,7 +285,7 @@ export class Processor extends Disposable {
|
||||
|
||||
/**
|
||||
* Add a job to the queue.
|
||||
* @param {Object} job The job.
|
||||
* @param {Job} job The job.
|
||||
*/
|
||||
_enqueue(job) {
|
||||
this._queue.push(job);
|
||||
@@ -255,48 +298,51 @@ export class Processor extends Disposable {
|
||||
* Dispatch a job.
|
||||
*/
|
||||
_dispatch() {
|
||||
if (this._running === 0 && this._queue.length > 0) {
|
||||
const job = this._queue.shift();
|
||||
this._job = job;
|
||||
const width = job.inputs[0].width;
|
||||
const height = job.inputs[0].height;
|
||||
const buffers = job.inputs.map(function (input) {
|
||||
return input.data.buffer;
|
||||
});
|
||||
const threads = this._workers.length;
|
||||
this._running = threads;
|
||||
if (threads === 1) {
|
||||
this._workers[0].postMessage(
|
||||
{
|
||||
buffers: buffers,
|
||||
meta: job.meta,
|
||||
imageOps: this._imageOps,
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
buffers
|
||||
);
|
||||
} else {
|
||||
const length = job.inputs[0].data.length;
|
||||
const segmentLength = 4 * Math.ceil(length / 4 / threads);
|
||||
for (let i = 0; i < threads; ++i) {
|
||||
const offset = i * segmentLength;
|
||||
const slices = [];
|
||||
for (let j = 0, jj = buffers.length; j < jj; ++j) {
|
||||
slices.push(buffers[j].slice(offset, offset + segmentLength));
|
||||
}
|
||||
this._workers[i].postMessage(
|
||||
{
|
||||
buffers: slices,
|
||||
meta: job.meta,
|
||||
imageOps: this._imageOps,
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
slices
|
||||
);
|
||||
}
|
||||
if (this._running || this._queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const job = this._queue.shift();
|
||||
this._job = job;
|
||||
const width = job.inputs[0].width;
|
||||
const height = job.inputs[0].height;
|
||||
const buffers = job.inputs.map(function (input) {
|
||||
return input.data.buffer;
|
||||
});
|
||||
const threads = this._workers.length;
|
||||
this._running = threads;
|
||||
if (threads === 1) {
|
||||
this._workers[0].postMessage(
|
||||
{
|
||||
buffers: buffers,
|
||||
meta: job.meta,
|
||||
imageOps: this._imageOps,
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
buffers
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const length = job.inputs[0].data.length;
|
||||
const segmentLength = 4 * Math.ceil(length / 4 / threads);
|
||||
for (let i = 0; i < threads; ++i) {
|
||||
const offset = i * segmentLength;
|
||||
const slices = [];
|
||||
for (let j = 0, jj = buffers.length; j < jj; ++j) {
|
||||
slices.push(buffers[j].slice(offset, offset + segmentLength));
|
||||
}
|
||||
this._workers[i].postMessage(
|
||||
{
|
||||
buffers: slices,
|
||||
meta: job.meta,
|
||||
imageOps: this._imageOps,
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
slices
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +376,7 @@ export class Processor extends Disposable {
|
||||
} else {
|
||||
const length = job.inputs[0].data.length;
|
||||
data = new Uint8ClampedArray(length);
|
||||
meta = new Array(length);
|
||||
meta = new Array(threads);
|
||||
const segmentLength = 4 * Math.ceil(length / 4 / threads);
|
||||
for (let i = 0; i < threads; ++i) {
|
||||
const buffer = this._dataLookup[i]['buffer'];
|
||||
@@ -384,14 +430,17 @@ export class Processor extends Disposable {
|
||||
*/
|
||||
const RasterEventType = {
|
||||
/**
|
||||
* Triggered before operations are run.
|
||||
* Triggered before operations are run. Listeners will receive an event object with
|
||||
* a `data` property that can be used to make data available to operations.
|
||||
* @event module:ol/source/Raster.RasterSourceEvent#beforeoperations
|
||||
* @api
|
||||
*/
|
||||
BEFOREOPERATIONS: 'beforeoperations',
|
||||
|
||||
/**
|
||||
* Triggered after operations are run.
|
||||
* Triggered after operations are run. Listeners will receive an event object with
|
||||
* a `data` property. If more than one thread is used, `data` will be an array of
|
||||
* objects. If a single thread is used, `data` will be a single object.
|
||||
* @event module:ol/source/Raster.RasterSourceEvent#afteroperations
|
||||
* @api
|
||||
*/
|
||||
@@ -407,6 +456,10 @@ const RasterOperationType = {
|
||||
IMAGE: 'image',
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import("./Image.js").ImageSourceEventTypes|'beforeoperations'|'afteroperations'} RasterSourceEventTypes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Events emitted by {@link module:ol/source/Raster} instances are instances of this
|
||||
@@ -416,7 +469,8 @@ export class RasterSourceEvent extends Event {
|
||||
/**
|
||||
* @param {string} type Type.
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState The frame state.
|
||||
* @param {Object} data An object made available to operations.
|
||||
* @param {Object|Array<Object>} data An object made available to operations. For "afteroperations" evenets
|
||||
* this will be an array of objects if more than one thread is used.
|
||||
*/
|
||||
constructor(type, frameState, data) {
|
||||
super(type);
|
||||
@@ -465,6 +519,16 @@ export class RasterSourceEvent extends Event {
|
||||
* be called with an array of ImageData objects from input sources.
|
||||
*/
|
||||
|
||||
/***
|
||||
* @template Return
|
||||
* @typedef {import("../Observable").OnSignature<import("../Observable").EventTypes, import("../events/Event.js").default, Return> &
|
||||
* import("../Observable").OnSignature<import("../ObjectEventType").Types, import("../Object").ObjectEvent, Return> &
|
||||
* import("../Observable").OnSignature<import("./Image.js").ImageSourceEventTypes, import("./Image.js").ImageSourceEvent, Return> &
|
||||
* import("../Observable").OnSignature<RasterSourceEventTypes, RasterSourceEvent, Return> &
|
||||
* import("../Observable").CombinedOnSignature<import("../Observable").EventTypes|import("../ObjectEventType").Types
|
||||
* |RasterSourceEventTypes, Return>} RasterSourceOnSignature
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* A source that transforms data from any number of input sources using an
|
||||
@@ -483,6 +547,21 @@ class RasterSource extends ImageSource {
|
||||
projection: null,
|
||||
});
|
||||
|
||||
/***
|
||||
* @type {RasterSourceOnSignature<import("../Observable.js").OnReturn>}
|
||||
*/
|
||||
this.on;
|
||||
|
||||
/***
|
||||
* @type {RasterSourceOnSignature<import("../Observable.js").OnReturn>}
|
||||
*/
|
||||
this.once;
|
||||
|
||||
/***
|
||||
* @type {RasterSourceOnSignature<void>}
|
||||
*/
|
||||
this.un;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Processor}
|
||||
@@ -639,7 +718,7 @@ class RasterSource extends ImageSource {
|
||||
frameState.extent = extent.slice();
|
||||
frameState.size[0] = Math.round(getWidth(extent) / resolution);
|
||||
frameState.size[1] = Math.round(getHeight(extent) / resolution);
|
||||
frameState.time = Infinity;
|
||||
frameState.time = Date.now();
|
||||
|
||||
const viewState = frameState.viewState;
|
||||
viewState.center = center;
|
||||
@@ -743,7 +822,7 @@ class RasterSource extends ImageSource {
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState The frame state.
|
||||
* @param {Error} err Any error during processing.
|
||||
* @param {ImageData} output The output image data.
|
||||
* @param {Object} data The user data.
|
||||
* @param {Object|Array<Object>} data The user data (or an array if more than one thread).
|
||||
* @private
|
||||
*/
|
||||
onWorkerComplete_(frameState, err, output, data) {
|
||||
@@ -783,13 +862,7 @@ class RasterSource extends ImageSource {
|
||||
this.dispatchEvent(
|
||||
new RasterSourceEvent(RasterEventType.AFTEROPERATIONS, frameState, data)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {null} not implemented
|
||||
*/
|
||||
getImageInternal() {
|
||||
return null; // not implemented
|
||||
requestAnimationFrame(this.changed.bind(this));
|
||||
}
|
||||
|
||||
disposeInternal() {
|
||||
|
||||
@@ -95,6 +95,7 @@ class Source extends BaseObject {
|
||||
/**
|
||||
* Get the attribution function for the source.
|
||||
* @return {?Attribution} Attribution function.
|
||||
* @api
|
||||
*/
|
||||
getAttributions() {
|
||||
return this.attributions_;
|
||||
@@ -102,6 +103,7 @@ class Source extends BaseObject {
|
||||
|
||||
/**
|
||||
* @return {boolean} Attributions are collapsible.
|
||||
* @api
|
||||
*/
|
||||
getAttributionsCollapsible() {
|
||||
return this.attributionsCollapsible_;
|
||||
|
||||
@@ -983,7 +983,7 @@ class VectorSource extends Source {
|
||||
}
|
||||
}
|
||||
this.loading =
|
||||
this.loader_ === VOID ? false : this.loadingExtentsCount_ > 0;
|
||||
this.loader_.length < 4 ? false : this.loadingExtentsCount_ > 0;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user