Merge branch 'master' into patch-4

This commit is contained in:
mike-000
2020-04-02 22:37:36 +01:00
committed by GitHub
78 changed files with 2959 additions and 1118 deletions

View File

@@ -41,6 +41,18 @@ See the following examples for more detail on bundling OpenLayers with your appl
* Using [Parcel](https://github.com/openlayers/ol-parcel) * Using [Parcel](https://github.com/openlayers/ol-parcel)
* Using [Browserify](https://github.com/openlayers/ol-browserify) * Using [Browserify](https://github.com/openlayers/ol-browserify)
## Sponsors
OpenLayers appreciates contributions of all kinds. We especially want to thank our fiscal sponsors who contribute to ongoing project maintenance.
![Pozi logo](./sponsor-logos/pozi.png)
> Pozi helps connect communities through spatial thinking.
> We love Openlayers and it forms a core part of our platform.
> https://pozi.com/ https://app.pozi.com/
See our [Open Collective](https://opencollective.com/openlayers/contribute/sponsors-214/checkout) page if you too are interested in becoming a regular sponsor.
## Supported Browsers ## Supported Browsers
OpenLayers runs on all modern browsers that support [HTML5](https://html.spec.whatwg.org/multipage/) and [ECMAScript 5](http://www.ecma-international.org/ecma-262/5.1/). This includes Chrome, Firefox, Safari and Edge. For older browsers and platforms like Internet Explorer (down to version 9) and Android 4.x, [polyfills](http://polyfill.io) for `requestAnimationFrame` and `Element.prototype.classList` are required, and using the KML format requires a polyfill for `URL`. OpenLayers runs on all modern browsers that support [HTML5](https://html.spec.whatwg.org/multipage/) and [ECMAScript 5](http://www.ecma-international.org/ecma-262/5.1/). This includes Chrome, Firefox, Safari and Edge. For older browsers and platforms like Internet Explorer (down to version 9) and Android 4.x, [polyfills](http://polyfill.io) for `requestAnimationFrame` and `Element.prototype.classList` are required, and using the KML format requires a polyfill for `URL`.

View File

@@ -1,8 +1,10 @@
## Upgrade notes ## Upgrade notes
### v6.2.0 ### v6.3.0
### v6.1.0 #### Vector source loading when extent crosses +/-180
Previously, when an extent crossed the date line, vector source loaders were called with an extent with 540 degrees of longitude. Now, two loader calls with the visible extent on both sides of the projection extent are issued. This should not require any application code changes, but may affect custom loaders.
### v6.0.0 ### v6.0.0

123
changelog/v6.3.0.md Normal file
View File

@@ -0,0 +1,123 @@
# 6.3.0
With more than 70 pull requests, this release not only brings significant improvements to the API documentation. It also fixes some old bugs and brings frequently requested improvments. And good news for TypeScript users: OpenLayers now ships with type definitions in `.d.ts` files.
## New features and improvements
* Several improvements to the Graticule layer, like consistent labeling, no more missing graticule lines, and it now works for views that cross the date line.
* Better support for KML icon colors, as well as fills and outlines in PolyStyle
* Better `ol/Overlay` performance and support for panning off-screen overlays into view
* Most of the rendering code can now be run in web workers, e.g. to render to an OffscreenCanvas
* OpenLayers now works fine in web components with shadow root
* WebGL point layers now support rotation based on feature attributes
## List of all changes
* [#10490](https://github.com/openlayers/openlayers/pull/10490) - Select style multiple select interactions removed ([@bepremeg](https://github.com/bepremeg))
* [#10531](https://github.com/openlayers/openlayers/pull/10531) - Dynamically chose the number of subdivisions based on the size of the Image to reproject ([@pjsg](https://github.com/pjsg))
* [#10618](https://github.com/openlayers/openlayers/pull/10618) - Add apidoc-debug task to debug the apidoc generation process ([@MoonE](https://github.com/MoonE))
* [#10343](https://github.com/openlayers/openlayers/pull/10343) - Correct interactions with circle geometries when using user coordinates ([@mike-000](https://github.com/mike-000))
* [#10864](https://github.com/openlayers/openlayers/pull/10864) - Update dependencies ([@ahocevar](https://github.com/ahocevar))
* [#10859](https://github.com/openlayers/openlayers/pull/10859) - Add an example of clipping layer based on a vector source ([@SDaron](https://github.com/SDaron))
* [#10850](https://github.com/openlayers/openlayers/pull/10850) - API docs for enums ([@ahocevar](https://github.com/ahocevar))
* [#10857](https://github.com/openlayers/openlayers/pull/10857) - Make OSM XML example work at dateline and replace Bing with MapTiler ([@mike-000](https://github.com/mike-000))
* [#10858](https://github.com/openlayers/openlayers/pull/10858) - Perform auto-pan when adding an Overlay to a Map ([@ejn](https://github.com/ejn))
* [#10646](https://github.com/openlayers/openlayers/pull/10646) - Write fill and outline in KML PolyStyle ([@mike-000](https://github.com/mike-000))
* [#10800](https://github.com/openlayers/openlayers/pull/10800) - Make Overlay.panIntoView an API method ([@ejn](https://github.com/ejn))
* [#10807](https://github.com/openlayers/openlayers/pull/10807) - Handle Graticule wrapX without calculating excess meridians ([@mike-000](https://github.com/mike-000))
* [#10795](https://github.com/openlayers/openlayers/pull/10795) - Show graticule labels in wrapped worlds ([@mike-000](https://github.com/mike-000))
* [#10824](https://github.com/openlayers/openlayers/pull/10824) - Fix drawing svg icon with color option in ie11 ([@MoonE](https://github.com/MoonE))
* [#10802](https://github.com/openlayers/openlayers/pull/10802) - Apidoc add default-exported enums ([@MoonE](https://github.com/MoonE))
* [#10805](https://github.com/openlayers/openlayers/pull/10805) - make ImageSourceEventType available for consumers ([@regileeso](https://github.com/regileeso))
* [#10822](https://github.com/openlayers/openlayers/pull/10822) - parsing color from IconStyle in KML files ([@lysek](https://github.com/lysek))
* [#10848](https://github.com/openlayers/openlayers/pull/10848) - Speed up Overlay element positioning using CSS translate() ([@horsenit](https://github.com/horsenit))
* [#9590](https://github.com/openlayers/openlayers/pull/9590) - Calculate tile grid extent from extent of bottom-level tile matrix ([@mloskot](https://github.com/mloskot))
* [#10845](https://github.com/openlayers/openlayers/pull/10845) - Fix createHitDetectionImageData error for features with no size ([@gedaiu](https://github.com/gedaiu))
* [#10842](https://github.com/openlayers/openlayers/pull/10842) - Fix custom symbol example short description ([@mike-000](https://github.com/mike-000))
* [#10828](https://github.com/openlayers/openlayers/pull/10828) - Offscreen canvas example ([@ahocevar](https://github.com/ahocevar))
* [#10816](https://github.com/openlayers/openlayers/pull/10816) - Add 'funding' field to `package.json` ([@marcjansen](https://github.com/marcjansen))
* [#10813](https://github.com/openlayers/openlayers/pull/10813) - Add sponsors section to the readme ([@tschaub](https://github.com/tschaub))
* [#10474](https://github.com/openlayers/openlayers/pull/10474) - Fix for undefined source in Image layer ([@mike-000](https://github.com/mike-000))
* [#10785](https://github.com/openlayers/openlayers/pull/10785) - Detect Zoomify server-side retina tiles ([@ahocevar](https://github.com/ahocevar))
* [#10787](https://github.com/openlayers/openlayers/pull/10787) - Improved projection extent in the "Reprojection with EPSG.io Search" example ([@mike-000](https://github.com/mike-000))
* [#10792](https://github.com/openlayers/openlayers/pull/10792) - Add support for EventListener Object ([@flexjoly](https://github.com/flexjoly))
* [#10777](https://github.com/openlayers/openlayers/pull/10777) - Keep the render loop running during simulation ([@ahocevar](https://github.com/ahocevar))
* [#10791](https://github.com/openlayers/openlayers/pull/10791) - iOS 12 touchmove: Prevent touchmove event default when no preceding pointer event ([@sosmo](https://github.com/sosmo))
* [#10786](https://github.com/openlayers/openlayers/pull/10786) - Resolve constraints when updating size ([@ahocevar](https://github.com/ahocevar))
* [#10788](https://github.com/openlayers/openlayers/pull/10788) - Add safeguard to handleTouchMove ([@sosmo](https://github.com/sosmo))
* [#10722](https://github.com/openlayers/openlayers/pull/10722) - fix: handle layer clear event in case clear(true) called ([@jellyedwards](https://github.com/jellyedwards))
* [#10723](https://github.com/openlayers/openlayers/pull/10723) - Improve the extent transforms used by Graticule and handle extents crossing the dateline ([@mike-000](https://github.com/mike-000))
* [#10744](https://github.com/openlayers/openlayers/pull/10744) - Ensure the Modify Features Test example opens at correct zoom ([@mike-000](https://github.com/mike-000))
* [#10767](https://github.com/openlayers/openlayers/pull/10767) - Replace Bing layer with MapTiler in examples ([@mike-000](https://github.com/mike-000))
* [#10751](https://github.com/openlayers/openlayers/pull/10751) - Sort events / observables in all cases ([@MoonE](https://github.com/MoonE))
* [#10763](https://github.com/openlayers/openlayers/pull/10763) - TypeScript: Fix inconsistent optionality in various APIs ([@jumpinjackie](https://github.com/jumpinjackie))
* [#10758](https://github.com/openlayers/openlayers/pull/10758) - Allow using feature attributes for symbol rotation in WebGL layers ([@jahow](https://github.com/jahow))
* [#10748](https://github.com/openlayers/openlayers/pull/10748) - Fix "Cannot read property 'anchor' of undefined" in ol/View ([@mike-000](https://github.com/mike-000))
* [#10746](https://github.com/openlayers/openlayers/pull/10746) - Fix building apidoc on windows ([@MoonE](https://github.com/MoonE))
* [#10720](https://github.com/openlayers/openlayers/pull/10720) - Apidoc better search ([@MoonE](https://github.com/MoonE))
* [#10743](https://github.com/openlayers/openlayers/pull/10743) - Ignore user provided tile cache size when too small ([@ahocevar](https://github.com/ahocevar))
* [#10736](https://github.com/openlayers/openlayers/pull/10736) - Allow cluster source to unlisten from its source ([@M393](https://github.com/M393))
* [#10739](https://github.com/openlayers/openlayers/pull/10739) - Fix typo in trackpad timeout ([@ahocevar](https://github.com/ahocevar))
* [#10740](https://github.com/openlayers/openlayers/pull/10740) - Document tabindex behavior for MouseWheelZoom and DragPan ([@matthias-ccri](https://github.com/matthias-ccri))
* [#10738](https://github.com/openlayers/openlayers/pull/10738) - Fix text background decluttering ([@ahocevar](https://github.com/ahocevar))
* [#10715](https://github.com/openlayers/openlayers/pull/10715) - Fix disappearing graticule labels when rotation returns to 0 ([@mike-000](https://github.com/mike-000))
* [#10713](https://github.com/openlayers/openlayers/pull/10713) - Draw graticule labels in a postrender function ([@mike-000](https://github.com/mike-000))
* [#10711](https://github.com/openlayers/openlayers/pull/10711) - Make sure that optional args are typed accordingly ([@ahocevar](https://github.com/ahocevar))
* [#10710](https://github.com/openlayers/openlayers/pull/10710) - Fix stylefunction return type ([@ahocevar](https://github.com/ahocevar))
* [#10709](https://github.com/openlayers/openlayers/pull/10709) - Fix type and documentation of style function ([@ahocevar](https://github.com/ahocevar))
* [#10708](https://github.com/openlayers/openlayers/pull/10708) - Handle Select interactions with falsey select style ([@ahocevar](https://github.com/ahocevar))
* [#10707](https://github.com/openlayers/openlayers/pull/10707) - Get default projection for overview map from main map. ([@AugustusKling](https://github.com/AugustusKling))
* [#10699](https://github.com/openlayers/openlayers/pull/10699) - Make Select interaction work when there are multiple instances ([@ahocevar](https://github.com/ahocevar))
* [#10694](https://github.com/openlayers/openlayers/pull/10694) - Draw image with configured opacity ([@M393](https://github.com/M393))
* [#10703](https://github.com/openlayers/openlayers/pull/10703) - CI and test fixes ([@ahocevar](https://github.com/ahocevar))
* [#10698](https://github.com/openlayers/openlayers/pull/10698) - Shadow root ([@ahocevar](https://github.com/ahocevar))
* [#10688](https://github.com/openlayers/openlayers/pull/10688) - Publish type definition files ([@ahocevar](https://github.com/ahocevar))
* [#10691](https://github.com/openlayers/openlayers/pull/10691) - Do not exceed color range ([@ahocevar](https://github.com/ahocevar))
* [#10683](https://github.com/openlayers/openlayers/pull/10683) - Dispatch enterfullscreen and leavefullscreen from the FullScreen control ([@fredj](https://github.com/fredj))
* [#10676](https://github.com/openlayers/openlayers/pull/10676) - Document that overviewmap view must use same projection as main map ([@mike-000](https://github.com/mike-000))
* [#10678](https://github.com/openlayers/openlayers/pull/10678) - Add maxResolution option to ol/tilegrid.createXYZ() and ol/source/XYZ ([@mike-000](https://github.com/mike-000))
* [#10690](https://github.com/openlayers/openlayers/pull/10690) - Document minZoom and maxZoom options for all layers ([@mike-000](https://github.com/mike-000))
* [#10672](https://github.com/openlayers/openlayers/pull/10672) - Nicer mousewheel and trackpad zooming ([@ahocevar](https://github.com/ahocevar))
* [#10687](https://github.com/openlayers/openlayers/pull/10687) - Increase timeout in listenImage test ([@fredj](https://github.com/fredj))
* [#10684](https://github.com/openlayers/openlayers/pull/10684) - perf: only do expensive reload when texture changes ([@jellyedwards](https://github.com/jellyedwards))
* [#10675](https://github.com/openlayers/openlayers/pull/10675) - typo ([@jipexu](https://github.com/jipexu))
* [#10669](https://github.com/openlayers/openlayers/pull/10669) - More browser compatible Export Map example ([@mike-000](https://github.com/mike-000))
* [#10667](https://github.com/openlayers/openlayers/pull/10667) - Do not render label with the current linedash ([@ahocevar](https://github.com/ahocevar))
* [#10666](https://github.com/openlayers/openlayers/pull/10666) - Load polyfill before example specific scripts in examples template ([@mike-000](https://github.com/mike-000))
* [#6526](https://github.com/openlayers/openlayers/pull/6526) - Draw interaction: add abortDrawing method and drawabort event ([@tchandelle](https://github.com/tchandelle))
* [#10657](https://github.com/openlayers/openlayers/pull/10657) - Changelog for v6.2.1 ([@openlayers](https://github.com/openlayers))
<details>
<summary>Dependency Updates</summary>
* [#10855](https://github.com/openlayers/openlayers/pull/10855) - Bump rollup from 2.1.0 to 2.3.0 ([@openlayers](https://github.com/openlayers))
* [#10854](https://github.com/openlayers/openlayers/pull/10854) - Bump ol-mapbox-style from 6.1.0 to 6.1.1 ([@openlayers](https://github.com/openlayers))
* [#10853](https://github.com/openlayers/openlayers/pull/10853) - Bump buble from 0.19.8 to 0.20.0 ([@openlayers](https://github.com/openlayers))
* [#10852](https://github.com/openlayers/openlayers/pull/10852) - Bump webpack from 4.42.0 to 4.42.1 ([@openlayers](https://github.com/openlayers))
* [#10837](https://github.com/openlayers/openlayers/pull/10837) - Bump ol-mapbox-style from 6.0.1 to 6.1.0 ([@openlayers](https://github.com/openlayers))
* [#10836](https://github.com/openlayers/openlayers/pull/10836) - Bump coveralls from 3.0.9 to 3.0.11 ([@openlayers](https://github.com/openlayers))
* [#10835](https://github.com/openlayers/openlayers/pull/10835) - Bump @babel/preset-env from 7.8.7 to 7.9.0 ([@openlayers](https://github.com/openlayers))
* [#10834](https://github.com/openlayers/openlayers/pull/10834) - Bump rollup from 1.32.1 to 2.1.0 ([@openlayers](https://github.com/openlayers))
* [#10833](https://github.com/openlayers/openlayers/pull/10833) - Bump fs-extra from 8.1.0 to 9.0.0 ([@openlayers](https://github.com/openlayers))
* [#10832](https://github.com/openlayers/openlayers/pull/10832) - Bump @babel/core from 7.8.7 to 7.9.0 ([@openlayers](https://github.com/openlayers))
* [#10831](https://github.com/openlayers/openlayers/pull/10831) - Bump babel-loader from 8.0.6 to 8.1.0 ([@openlayers](https://github.com/openlayers))
* [#10830](https://github.com/openlayers/openlayers/pull/10830) - Bump mocha from 7.1.0 to 7.1.1 ([@openlayers](https://github.com/openlayers))
* [#10829](https://github.com/openlayers/openlayers/pull/10829) - Bump marked from 0.8.0 to 0.8.2 ([@openlayers](https://github.com/openlayers))
* [#10811](https://github.com/openlayers/openlayers/pull/10811) - Bump sinon from 9.0.0 to 9.0.1 ([@openlayers](https://github.com/openlayers))
* [#10810](https://github.com/openlayers/openlayers/pull/10810) - Bump rollup-plugin-terser from 5.2.0 to 5.3.0 ([@openlayers](https://github.com/openlayers))
* [#10809](https://github.com/openlayers/openlayers/pull/10809) - Bump yargs from 15.3.0 to 15.3.1 ([@openlayers](https://github.com/openlayers))
* [#10806](https://github.com/openlayers/openlayers/pull/10806) - [Security] Bump acorn from 6.1.1 to 6.4.1 ([@openlayers](https://github.com/openlayers))
* [#10755](https://github.com/openlayers/openlayers/pull/10755) - Bump rollup from 1.31.1 to 1.32.0 ([@openlayers](https://github.com/openlayers))
* [#10754](https://github.com/openlayers/openlayers/pull/10754) - Bump @babel/preset-env from 7.8.4 to 7.8.6 ([@openlayers](https://github.com/openlayers))
* [#10753](https://github.com/openlayers/openlayers/pull/10753) - Bump mocha from 7.0.1 to 7.1.0 ([@openlayers](https://github.com/openlayers))
* [#10752](https://github.com/openlayers/openlayers/pull/10752) - Bump @babel/core from 7.8.4 to 7.8.6 ([@openlayers](https://github.com/openlayers))
* [#10725](https://github.com/openlayers/openlayers/pull/10725) - Bump elm-pep from 1.0.4 to 1.0.6 ([@openlayers](https://github.com/openlayers))
* [#10726](https://github.com/openlayers/openlayers/pull/10726) - Bump sinon from 8.1.1 to 9.0.0 ([@openlayers](https://github.com/openlayers))
* [#10680](https://github.com/openlayers/openlayers/pull/10680) - Bump terser-webpack-plugin from 2.3.4 to 2.3.5 ([@openlayers](https://github.com/openlayers))
* [#10682](https://github.com/openlayers/openlayers/pull/10682) - Bump webpack from 4.41.5 to 4.41.6 ([@openlayers](https://github.com/openlayers))
* [#10681](https://github.com/openlayers/openlayers/pull/10681) - Bump webpack-cli from 3.3.10 to 3.3.11 ([@openlayers](https://github.com/openlayers))
* [#10679](https://github.com/openlayers/openlayers/pull/10679) - Bump rollup from 1.31.0 to 1.31.1 ([@openlayers](https://github.com/openlayers))
</details>

View File

@@ -73,9 +73,6 @@ function includeAugments(doclet) {
}); });
} }
cls._hideConstructor = true; cls._hideConstructor = true;
if (!cls.undocumented) {
cls._documented = true;
}
} }
} }
} }
@@ -113,9 +110,10 @@ const moduleRoot = path.join(process.cwd(), 'src');
// Tag default exported Identifiers because their name should be the same as the module name. // Tag default exported Identifiers because their name should be the same as the module name.
exports.astNodeVisitor = { exports.astNodeVisitor = {
visitNode: function(node, e, parser, currentSourceName) { visitNode: function(node, e, parser, currentSourceName) {
if (node.type === 'Identifier' && node.parent.type === 'ExportDefaultDeclaration') { if (node.parent && node.parent.type === 'ExportDefaultDeclaration') {
const modulePath = path.relative(moduleRoot, currentSourceName).replace(/\.js$/, ''); const modulePath = path.relative(moduleRoot, currentSourceName).replace(/\.js$/, '');
defaultExports['module:' + modulePath.replace(/\\/g, '/') + '~' + node.name] = true; const exportName = 'module:' + modulePath.replace(/\\/g, '/') + (node.name ? '~' + node.name : '');
defaultExports[exportName] = true;
} }
} }
}; };
@@ -156,6 +154,7 @@ exports.handlers = {
parseComplete: function(e) { parseComplete: function(e) {
const doclets = e.doclets; const doclets = e.doclets;
const byLongname = doclets.index.longname;
for (let i = doclets.length - 1; i >= 0; --i) { for (let i = doclets.length - 1; i >= 0; --i) {
const doclet = doclets[i]; const doclet = doclets[i];
if (doclet.stability) { if (doclet.stability) {
@@ -180,12 +179,14 @@ exports.handlers = {
doclet._hideConstructor = true; doclet._hideConstructor = true;
includeAugments(doclet); includeAugments(doclet);
sortOtherMembers(doclet); sortOtherMembers(doclet);
} else if (!doclet._hideConstructor && !(doclet.kind == 'typedef' && doclet.longname in types)) { } else if (!doclet._hideConstructor) {
// Remove all other undocumented symbols // Remove all other undocumented symbols
doclet.undocumented = true; doclet.undocumented = true;
} }
if (doclet._documented) { if (doclet.memberof && byLongname[doclet.memberof] &&
delete doclet.undocumented; byLongname[doclet.memberof][0].isEnum &&
!byLongname[doclet.memberof][0].properties.some(p => p.stability)) {
byLongname[doclet.memberof][0].undocumented = true;
} }
} }
}, },

View File

@@ -64,7 +64,7 @@ var self = this;
<ul><?js fires.forEach(function(f) { <ul><?js fires.forEach(function(f) {
var parts = f.split(/#?event:/); var parts = f.split(/#?event:/);
var type = parts.pop(); var type = parts.pop();
var eventClassName = parts[0]; var eventClass = self.find({longname: parts[0]})[0];
parts = type.split(' '); parts = type.split(' ');
type = parts.shift(); type = parts.shift();
var description = parts.length ? parts.join(' ') : ''; var description = parts.length ? parts.join(' ') : '';
@@ -74,12 +74,9 @@ var self = this;
} }
?> ?>
<li class="<?js= (eventDoclet || data).stability !== 'stable' ? 'unstable' : '' ?>"> <li class="<?js= (eventDoclet || data).stability !== 'stable' ? 'unstable' : '' ?>">
<code><?js= eventClassName ? self.linkto(f, type) : type ?></code> <code><?js= eventClass ? self.linkto(f, type) : type ?></code>
<?js if (eventClassName) { <?js if (eventClass) { ?>
var eventClass = self.find({longname: eventClassName})[0]; (<?js= self.linkto(eventClass.longname) ?>)
if (eventClass) { ?>
(<?js= self.linkto(eventClass.longname) ?>)
<?js } ?>
<?js } ?> <?js } ?>
<?js= self.partial('stability.tmpl', eventDoclet || (data.stability ? data : {})) ?> <?js= self.partial('stability.tmpl', eventDoclet || (data.stability ? data : {})) ?>
<?js if (description) { ?> - <?js if (description) { ?> -

View File

@@ -6,7 +6,7 @@ docs: >
<p>When the Bing Maps tile service doesn't have tiles for a given resolution and region it returns "placeholder" tiles indicating that. Zoom the map beyond level 19 to see the "placeholder" tiles. If you want OpenLayers to display stretched tiles in place of "placeholder" tiles beyond zoom level 19 then set <code>maxZoom</code> to <code>19</code> in the options passed to <code>ol/source/BingMaps</code>.</p> <p>When the Bing Maps tile service doesn't have tiles for a given resolution and region it returns "placeholder" tiles indicating that. Zoom the map beyond level 19 to see the "placeholder" tiles. If you want OpenLayers to display stretched tiles in place of "placeholder" tiles beyond zoom level 19 then set <code>maxZoom</code> to <code>19</code> in the options passed to <code>ol/source/BingMaps</code>.</p>
tags: "bing, bing-maps" tags: "bing, bing-maps"
cloak: cloak:
- key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5 - key: ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp
value: Your Bing Maps Key from http://www.bingmapsportal.com/ here value: Your Bing Maps Key from http://www.bingmapsportal.com/ here
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>

View File

@@ -18,7 +18,7 @@ for (i = 0, ii = styles.length; i < ii; ++i) {
visible: false, visible: false,
preload: Infinity, preload: Infinity,
source: new BingMaps({ source: new BingMaps({
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5', key: 'ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp ',
imagerySet: styles[i] imagerySet: styles[i]
// use maxZoom 19 to see stretched tiles instead of the BingMaps // use maxZoom 19 to see stretched tiles instead of the BingMaps
// "no photos at this zoom level" tiles // "no photos at this zoom level" tiles

View File

@@ -2,6 +2,6 @@
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" xmlns="http://www.w3.org/2000/svg">
<g> <g>
<rect width="20" height="20" style="fill:#fff" /> <rect width="20" height="20" style="fill:#fff; stroke-width:4px; stroke:#000" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 194 B

After

Width:  |  Height:  |  Size: 225 B

View File

@@ -1,7 +1,7 @@
--- ---
layout: example.html layout: example.html
title: Earthquakes with custom symbols title: Earthquakes with custom symbols
shortdesc: Demonstrates the use of `toCanvas` to create custom icon symbols. shortdesc: Demonstrates the use of `toContext` to create custom icon symbols.
docs: > docs: >
This example parses a KML file and renders the features as a vector layer. The layer is given a <code>style</code> that renders earthquake locations with a custom lightning symbol and a size relative to their magnitude. This example parses a KML file and renders the features as a vector layer. The layer is given a <code>style</code> that renders earthquake locations with a custom lightning symbol and a size relative to their magnitude.
tags: "KML, vector, style, canvas, symbol" tags: "KML, vector, style, canvas, symbol"

View File

@@ -25,6 +25,7 @@ rome.setStyle(new Style({
image: new Icon({ image: new Icon({
color: '#8959A8', color: '#8959A8',
crossOrigin: 'anonymous', crossOrigin: 'anonymous',
imgSize: [20, 20],
src: 'data/square.svg' src: 'data/square.svg'
}) })
})); }));

View File

@@ -0,0 +1,3 @@
#map {
background: transparent;
}

View File

@@ -0,0 +1,9 @@
---
layout: example.html
title: Vector Clipping Layer
shortdesc: Vector Clipping Layer example
docs: >
Example of a clipping layer based on a vector source
tags: "clipping, openstreetmap, vector"
---
<div id="map" class="map"></div>

View File

@@ -0,0 +1,46 @@
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
import VectorSource from '../src/ol/source/Vector.js';
import GeoJSON from '../src/ol/format/GeoJSON.js';
import OSM from '../src/ol/source/OSM.js';
import {Fill, Style} from '../src/ol/style.js';
import {getVectorContext} from '../src/ol/render.js';
import {fromLonLat} from '../src/ol/proj.js';
const base = new TileLayer({
source: new OSM()
});
const clipLayer = new VectorLayer({
style: null,
source: new VectorSource({
url:
'./data/geojson/switzerland.geojson',
format: new GeoJSON()
})
});
const style = new Style({
fill: new Fill({
color: 'black'
})
});
base.on('postrender', function(e) {
e.context.globalCompositeOperation = 'destination-in';
const vectorContext = getVectorContext(e);
clipLayer.getSource().forEachFeature(function(feature) {
vectorContext.drawFeature(feature, style);
});
e.context.globalCompositeOperation = 'source-over';
});
const map = new Map({
layers: [base, clipLayer],
target: 'map',
view: new View({
center: fromLonLat([8.23, 46.86]),
zoom: 7
})
});

View File

@@ -0,0 +1,6 @@
.ol-rotate {
left: .5em;
bottom: .5em;
top: unset;
right: unset;
}

View File

@@ -1,5 +1,5 @@
--- ---
layout: example-verbatim.html layout: example.html
title: Vector tiles created from a Mapbox Style object title: Vector tiles created from a Mapbox Style object
shortdesc: Example of using ol-mapbox-style with tiles from maptiler.com. shortdesc: Example of using ol-mapbox-style with tiles from maptiler.com.
docs: > docs: >
@@ -10,27 +10,4 @@ cloak:
- key: get_your_own_D6rA4zTHduk6KOKTXzGB - key: get_your_own_D6rA4zTHduk6KOKTXzGB
value: Get your own API key at https://www.maptiler.com/cloud/ value: Get your own API key at https://www.maptiler.com/cloud/
--- ---
<!doctype html> <div id="map" class="map"></div>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<title>Mapbox Style objects with ol-mapbox-style</title>
<link rel="stylesheet" href="../css/ol.css" type="text/css">
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=fetch,String.prototype.startsWith,Object.assign"></script>
<style type="text/css">
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map" class="map"></div>
<script src="common.js"></script>
<script src="mapbox-style.js"></script>
</body>
</html>

View File

@@ -1,3 +1,6 @@
import apply from 'ol-mapbox-style'; import apply from 'ol-mapbox-style';
import FullScreen from '../src/ol/control/FullScreen.js';
apply('map', 'https://api.maptiler.com/maps/topo/style.json?key=get_your_own_D6rA4zTHduk6KOKTXzGB'); apply('map', 'https://api.maptiler.com/maps/topo/style.json?key=get_your_own_D6rA4zTHduk6KOKTXzGB').then(function(map) {
map.addControl(new FullScreen());
});

View File

@@ -4,7 +4,7 @@ title: Full-Screen Mobile
shortdesc: Example of a full screen map. shortdesc: Example of a full screen map.
tags: "fullscreen, geolocation, mobile" tags: "fullscreen, geolocation, mobile"
cloak: cloak:
- key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5 - key: ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp
value: Your Bing Maps Key from http://www.bingmapsportal.com/ here value: Your Bing Maps Key from http://www.bingmapsportal.com/ here
--- ---
<!doctype html> <!doctype html>

View File

@@ -14,7 +14,7 @@ const map = new Map({
layers: [ layers: [
new TileLayer({ new TileLayer({
source: new BingMaps({ source: new BingMaps({
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5', key: 'ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp ',
imagerySet: 'RoadOnDemand' imagerySet: 'RoadOnDemand'
}) })
}) })

View File

@@ -0,0 +1,9 @@
.map {
background: rgba(232, 230, 223, 1);
}
.ol-rotate {
left: .5em;
bottom: .5em;
top: unset;
right: unset;
}

View File

@@ -0,0 +1,10 @@
---
layout: example.html
title: Vector tiles rendered in an offscreen canvas
shortdesc: Example of a map that delegates rendering to a worker.
docs: >
The map in this example is rendered in a web worker, using `OffscreenCanvas`. **Note:** This is currently only supported in Chrome and Edge.
tags: "worker, offscreencanvas, vector-tiles"
experimental: true
---
<div id="map" class="map"></div>

View File

@@ -0,0 +1,125 @@
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import Layer from '../src/ol/layer/Layer.js';
import Worker from 'worker-loader!./offscreen-canvas.worker.js'; //eslint-disable-line
import {compose, create} from '../src/ol/transform.js';
import {createTransformString} from '../src/ol/render/canvas.js';
import {createXYZ} from '../src/ol/tilegrid.js';
import {FullScreen} from '../src/ol/control.js';
import stringify from 'json-stringify-safe';
import Source from '../src/ol/source/Source.js';
const worker = new Worker();
let container, transformContainer, canvas, rendering, workerFrameState, mainThreadFrameState;
// Transform the container to account for the differnece between the (newer)
// main thread frameState and the (older) worker frameState
function updateContainerTransform() {
if (workerFrameState) {
const viewState = mainThreadFrameState.viewState;
const renderedViewState = workerFrameState.viewState;
const center = viewState.center;
const resolution = viewState.resolution;
const rotation = viewState.rotation;
const renderedCenter = renderedViewState.center;
const renderedResolution = renderedViewState.resolution;
const renderedRotation = renderedViewState.rotation;
const transform = create();
// Skip the extra transform for rotated views, because it will not work
// correctly in that case
if (!rotation) {
compose(transform,
(renderedCenter[0] - center[0]) / resolution,
(center[1] - renderedCenter[1]) / resolution,
renderedResolution / resolution, renderedResolution / resolution,
rotation - renderedRotation,
0, 0);
}
transformContainer.style.transform = createTransformString(transform);
}
}
const map = new Map({
layers: [
new Layer({
render: function(frameState) {
if (!container) {
container = document.createElement('div');
container.style.position = 'absolute';
container.style.width = '100%';
container.style.height = '100%';
transformContainer = document.createElement('div');
transformContainer.style.position = 'absolute';
transformContainer.style.width = '100%';
transformContainer.style.height = '100%';
container.appendChild(transformContainer);
canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
canvas.style.left = '0';
canvas.style.transformOrigin = 'top left';
transformContainer.appendChild(canvas);
}
mainThreadFrameState = frameState;
updateContainerTransform();
if (!rendering) {
rendering = true;
worker.postMessage({
action: 'render',
frameState: JSON.parse(stringify(frameState))
});
} else {
frameState.animate = true;
}
return container;
},
source: new Source({
attributions: [
'<a href="https://www.maptiler.com/copyright/" target="_blank">© MapTiler</a>',
'<a href="https://www.openstreetmap.org/copyright" target="_blank">© OpenStreetMap contributors</a>'
]
})
})
],
target: 'map',
view: new View({
resolutions: createXYZ({tileSize: 512}).getResolutions89,
center: [0, 0],
zoom: 2
})
});
map.addControl(new FullScreen());
// Worker messaging and actions
worker.addEventListener('message', message => {
if (message.data.action === 'loadImage') {
// Image loader for ol-mapbox-style
const image = new Image();
image.crossOrigin = 'anonymous';
image.addEventListener('load', function() {
createImageBitmap(image, 0, 0, image.width, image.height).then(imageBitmap => {
worker.postMessage({
action: 'imageLoaded',
image: imageBitmap,
src: message.data.src
}, [imageBitmap]);
});
});
image.src = event.data.src;
} else if (message.data.action === 'requestRender') {
// Worker requested a new render frame
map.render();
} else if (canvas && message.data.action === 'rendered') {
// Worker provies a new render frame
requestAnimationFrame(function() {
const imageData = message.data.imageData;
canvas.width = imageData.width;
canvas.height = imageData.height;
canvas.getContext('2d').drawImage(imageData, 0, 0);
canvas.style.transform = message.data.transform;
workerFrameState = message.data.frameState;
updateContainerTransform();
});
rendering = false;
}
});

View File

@@ -0,0 +1,136 @@
import VectorTileLayer from '../src/ol/layer/VectorTile.js';
import VectorTileSource from '../src/ol/source/VectorTile.js';
import MVT from '../src/ol/format/MVT.js';
import {Projection} from '../src/ol/proj.js';
import TileQueue from '../src/ol/TileQueue.js';
import {getTilePriority as tilePriorityFunction} from '../src/ol/TileQueue.js';
import {renderDeclutterItems} from '../src/ol/render.js';
import styleFunction from 'ol-mapbox-style/dist/stylefunction.js';
import {inView} from '../src/ol/layer/Layer.js';
import stringify from 'json-stringify-safe';
/** @type {any} */
const worker = self;
let frameState, pixelRatio, rendererTransform;
const canvas = new OffscreenCanvas(1, 1);
// OffscreenCanvas does not have a style, so we mock it
canvas.style = {};
const context = canvas.getContext('2d');
const sources = {
landcover: new VectorTileSource({
maxZoom: 9,
format: new MVT(),
url: 'https://api.maptiler.com/tiles/landcover/{z}/{x}/{y}.pbf?key=get_your_own_D6rA4zTHduk6KOKTXzGB'
}),
contours: new VectorTileSource({
minZoom: 9,
maxZoom: 14,
format: new MVT(),
url: 'https://api.maptiler.com/tiles/contours/{z}/{x}/{y}.pbf?key=get_your_own_D6rA4zTHduk6KOKTXzGB'
}),
openmaptiles: new VectorTileSource({
format: new MVT(),
maxZoom: 14,
url: 'https://api.maptiler.com/tiles/v3/{z}/{x}/{y}.pbf?key=get_your_own_D6rA4zTHduk6KOKTXzGB'
})
};
const layers = [];
// Font replacement so we do not need to load web fonts in the worker
function getFont(font) {
return font[0]
.replace('Noto Sans', 'serif')
.replace('Roboto', 'sans-serif');
}
function loadStyles() {
const styleUrl = 'https://api.maptiler.com/maps/topo/style.json?key=get_your_own_D6rA4zTHduk6KOKTXzGB';
fetch(styleUrl).then(data => data.json()).then(styleJson => {
const buckets = [];
let currentSource;
styleJson.layers.forEach(layer => {
if (!layer.source) {
return;
}
if (currentSource !== layer.source) {
currentSource = layer.source;
buckets.push({
source: layer.source,
layers: []
});
}
buckets[buckets.length - 1].layers.push(layer.id);
});
const spriteUrl = styleJson.sprite + (pixelRatio > 1 ? '@2x' : '') + '.json';
const spriteImageUrl = styleJson.sprite + (pixelRatio > 1 ? '@2x' : '') + '.png';
fetch(spriteUrl).then(data => data.json()).then(spriteJson => {
buckets.forEach(bucket => {
const source = sources[bucket.source];
if (!source) {
return;
}
const layer = new VectorTileLayer({
declutter: true,
source,
minZoom: source.getTileGrid().getMinZoom()
});
layer.getRenderer().useContainer = function(target, transform) {
this.containerReused = this.getLayer() !== layers[0];
this.canvas = canvas;
this.context = context;
this.container = {
firstElementChild: canvas
};
rendererTransform = transform;
};
styleFunction(layer, styleJson, bucket.layers, undefined, spriteJson, spriteImageUrl, getFont);
layers.push(layer);
});
worker.postMessage({action: 'requestRender'});
});
});
}
// Minimal map-like functionality for rendering
const tileQueue = new TileQueue(
(tile, tileSourceKey, tileCenter, tileResolution) => tilePriorityFunction(frameState, tile, tileSourceKey, tileCenter, tileResolution),
() => worker.postMessage({action: 'requestRender'}));
const maxTotalLoading = 8;
const maxNewLoads = 2;
worker.addEventListener('message', event => {
if (event.data.action !== 'render') {
return;
}
frameState = event.data.frameState;
if (!pixelRatio) {
pixelRatio = frameState.pixelRatio;
loadStyles();
}
frameState.tileQueue = tileQueue;
frameState.viewState.projection.__proto__ = Projection.prototype;
layers.forEach(layer => {
if (inView(layer.getLayerState(), frameState.viewState)) {
const renderer = layer.getRenderer();
renderer.renderFrame(frameState, canvas);
}
});
renderDeclutterItems(frameState, null);
if (tileQueue.getTilesLoading() < maxTotalLoading) {
tileQueue.reprioritize();
tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
}
const imageData = canvas.transferToImageBitmap();
worker.postMessage({
action: 'rendered',
imageData: imageData,
transform: rendererTransform,
frameState: JSON.parse(stringify(frameState))
}, [imageData]);
});

View File

@@ -6,7 +6,7 @@ docs: >
<p>The map on the top preloads low resolution tiles. The map on the bottom does not use any preloading. Try zooming out and panning to see the difference.</p> <p>The map on the top preloads low resolution tiles. The map on the bottom does not use any preloading. Try zooming out and panning to see the difference.</p>
tags: "preload, bing" tags: "preload, bing"
cloak: cloak:
- key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5 - key: ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp
value: Your Bing Maps Key from http://www.bingmapsportal.com/ here value: Your Bing Maps Key from http://www.bingmapsportal.com/ here
--- ---
<div id="map1" class="map"></div> <div id="map1" class="map"></div>

View File

@@ -14,7 +14,7 @@ const map1 = new Map({
new TileLayer({ new TileLayer({
preload: Infinity, preload: Infinity,
source: new BingMaps({ source: new BingMaps({
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5', key: 'ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp ',
imagerySet: 'Aerial' imagerySet: 'Aerial'
}) })
}) })
@@ -28,7 +28,7 @@ const map2 = new Map({
new TileLayer({ new TileLayer({
preload: 0, // default value preload: 0, // default value
source: new BingMaps({ source: new BingMaps({
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5', key: 'ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp ',
imagerySet: 'AerialWithLabelsOnDemand' imagerySet: 'AerialWithLabelsOnDemand'
}) })
}) })

View File

@@ -57,6 +57,8 @@
event.preventDefault(); event.preventDefault();
const html = document.getElementById('example-html-source').innerText; const html = document.getElementById('example-html-source').innerText;
const js = document.getElementById('example-js-source').innerText; const js = document.getElementById('example-js-source').innerText;
const workerContainer = document.getElementById('example-worker-source');
const worker = workerContainer ? workerContainer.innerText : undefined;
const pkgJson = document.getElementById('example-pkg-source').innerText; const pkgJson = document.getElementById('example-pkg-source').innerText;
const form = document.getElementById('codepen-form'); const form = document.getElementById('codepen-form');
@@ -68,22 +70,28 @@
Promise.all(promises) Promise.all(promises)
.then(results => { .then(results => {
const data = { const files = {
files: { 'index.html': {
'index.html': { content: html
content: html },
}, 'index.js': {
'index.js': { content: js
content: js },
}, "package.json": {
"package.json": { content: pkgJson
content: pkgJson },
}, 'sandbox.config.json': {
'sandbox.config.json': { content: '{"template": "parcel"}'
content: '{"template": "parcel"}'
}
} }
}; };
if (worker) {
files['worker.js'] = {
content: worker
}
}
const data = {
files: files
};
for (let i = 0; i < localResources.length; i++) { for (let i = 0; i < localResources.length; i++) {
data.files[localResources[i]] = results[i]; data.files[localResources[i]] = results[i];

View File

@@ -309,4 +309,4 @@ function createMapboxStreetsV6Style(Style, Fill, Stroke, Icon, Text) {
styles.length = length; styles.length = length;
return styles; return styles;
}; };
} }

View File

@@ -160,6 +160,14 @@
<pre><legend>index.js</legend><code id="example-js-source" class="language-js">import 'ol/ol.css'; <pre><legend>index.js</legend><code id="example-js-source" class="language-js">import 'ol/ol.css';
{{ js.source }}</code></pre> {{ js.source }}</code></pre>
</div> </div>
{{#if worker.source}}
<div class="row-fluid">
<div class="source-controls">
<a class="copy-button" id="copy-worker-button" data-clipboard-target="#example-worker-source"><i class="fa fa-clipboard"></i> Copy</a>
</div>
<pre><legend>worker.js</legend><code id="example-worker-source" class="language-js">{{ worker.source }}</code></pre>
</div>
{{/if}}
<div class="row-fluid"> <div class="row-fluid">
<div class="source-controls"> <div class="source-controls">
<a class="copy-button" id="copy-pkg-button" data-clipboard-target="#example-pkg-source"><i class="fa fa-clipboard"></i> Copy</a> <a class="copy-button" id="copy-pkg-button" data-clipboard-target="#example-pkg-source"><i class="fa fa-clipboard"></i> Copy</a>
@@ -167,7 +175,6 @@
<pre><legend>package.json</legend><code id="example-pkg-source" class="language-js">{{ pkgJson }}</code></pre> <pre><legend>package.json</legend><code id="example-pkg-source" class="language-js">{{ pkgJson }}</code></pre>
</div> </div>
</div> </div>
<script src="./resources/common.js"></script> <script src="./resources/common.js"></script>
<script src="./resources/prism/prism.min.js"></script> <script src="./resources/prism/prism.min.js"></script>
{{{ js.tag }}} {{{ js.tag }}}

View File

@@ -4,9 +4,9 @@ title: OSM XML
shortdesc: Example of using the OSM XML source. shortdesc: Example of using the OSM XML source.
docs: > docs: >
OSM XML vector data is loaded dynamically from a the [Overpass API](http://overpass-api.de) using a bbox strategy. Note that panning and zooming will eventually lead to "Too many requests" errors from the Overpass API. OSM XML vector data is loaded dynamically from a the [Overpass API](http://overpass-api.de) using a bbox strategy. Note that panning and zooming will eventually lead to "Too many requests" errors from the Overpass API.
tags: "vector, osmxml, loading, server, strategy, bbox" tags: "vector, osmxml, loading, server, strategy, bbox, maptiler"
cloak: cloak:
- key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5 - key: get_your_own_D6rA4zTHduk6KOKTXzGB
value: Your Bing Maps Key from http://www.bingmapsportal.com/ here value: Get your own API key at https://www.maptiler.com/cloud/
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>

View File

@@ -4,7 +4,7 @@ import OSMXML from '../src/ol/format/OSMXML.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
import {bbox as bboxStrategy} from '../src/ol/loadingstrategy.js'; import {bbox as bboxStrategy} from '../src/ol/loadingstrategy.js';
import {transformExtent} from '../src/ol/proj.js'; import {transformExtent} from '../src/ol/proj.js';
import BingMaps from '../src/ol/source/BingMaps.js'; import XYZ from '../src/ol/source/XYZ.js';
import VectorSource from '../src/ol/source/Vector.js'; import VectorSource from '../src/ol/source/Vector.js';
import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js'; import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js';
@@ -85,8 +85,8 @@ const vectorSource = new VectorSource({
vectorSource.addFeatures(features); vectorSource.addFeatures(features);
}); });
const query = '(node(' + const query = '(node(' +
epsg4326Extent[1] + ',' + epsg4326Extent[0] + ',' + epsg4326Extent[1] + ',' + Math.max(epsg4326Extent[0], -180) + ',' +
epsg4326Extent[3] + ',' + epsg4326Extent[2] + epsg4326Extent[3] + ',' + Math.min(epsg4326Extent[2], 180) +
');rel(bn)->.foo;way(bn);node(w)->.foo;rel(bw););out meta;'; ');rel(bn)->.foo;way(bn);node(w)->.foo;rel(bw););out meta;';
client.send(query); client.send(query);
}, },
@@ -110,10 +110,15 @@ const vector = new VectorLayer({
} }
}); });
const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
const attributions = '<a href="https://www.maptiler.com/copyright/" target="_blank">&copy; MapTiler</a> ' +
'<a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>';
const raster = new TileLayer({ const raster = new TileLayer({
source: new BingMaps({ source: new XYZ({
imagerySet: 'Aerial', attributions: attributions,
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5' url: 'https://api.maptiler.com/tiles/satellite/{z}/{x}/{y}.jpg?key=' + key,
maxZoom: 20
}) })
}); });

View File

@@ -208,6 +208,10 @@ ExampleBuilder.prototype.render = async function(dir, chunk) {
jsSource = jsSource.replace(new RegExp(entry.key, 'g'), entry.value); jsSource = jsSource.replace(new RegExp(entry.key, 'g'), entry.value);
} }
} }
// Remove worker loader import and modify `new Worker()` to add source
jsSource = jsSource.replace(/import Worker from 'worker-loader![^\n]*\n/g, '');
jsSource = jsSource.replace('new Worker()', 'new Worker(\'./worker.js\')');
data.js = { data.js = {
tag: `<script src="${this.common}.js"></script><script src="${jsName}"></script>`, tag: `<script src="${this.common}.js"></script><script src="${jsName}"></script>`,
source: jsSource source: jsSource
@@ -218,9 +222,33 @@ ExampleBuilder.prototype.render = async function(dir, chunk) {
data.js.tag = prelude + data.js.tag; data.js.tag = prelude + data.js.tag;
} }
// check for worker js
const workerName = `${name}.worker.js`;
const workerPath = path.join(dir, workerName);
let workerSource;
try {
workerSource = await readFile(workerPath, readOptions);
} catch (err) {
// pass
}
if (workerSource) {
// remove "../src/" prefix and ".js" to have the same import syntax as the documentation
workerSource = workerSource.replace(/'\.\.\/src\//g, '\'');
workerSource = workerSource.replace(/\.js';/g, '\';');
if (data.cloak) {
for (const entry of data.cloak) {
workerSource = workerSource.replace(new RegExp(entry.key, 'g'), entry.value);
}
}
data.worker = {
source: workerSource
};
assets[workerName] = workerSource;
}
data.pkgJson = JSON.stringify({ data.pkgJson = JSON.stringify({
name: name, name: name,
dependencies: getDependencies(jsSource), dependencies: getDependencies(jsSource + workerSource ? `\n${workerSource}` : ''),
devDependencies: { devDependencies: {
parcel: '1.11.0' parcel: '1.11.0'
}, },

1423
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "ol", "name": "ol",
"version": "6.2.2", "version": "6.3.1-dev",
"description": "OpenLayers mapping library", "description": "OpenLayers mapping library",
"keywords": [ "keywords": [
"map", "map",
@@ -24,6 +24,7 @@
"copy-css": "shx cp src/ol/ol.css build/ol/ol.css", "copy-css": "shx cp src/ol/ol.css build/ol/ol.css",
"transpile": "shx rm -rf build/ol && shx mkdir -p build/ol && shx cp -rf src/ol build/ol/src && node tasks/serialize-workers && npx --package typescript@3.8.1-rc tsc --project config/tsconfig-build.json", "transpile": "shx rm -rf build/ol && shx mkdir -p build/ol && shx cp -rf src/ol build/ol/src && node tasks/serialize-workers && npx --package typescript@3.8.1-rc tsc --project config/tsconfig-build.json",
"typecheck": "tsc --pretty", "typecheck": "tsc --pretty",
"apidoc-debug": "shx rm -rf build/apidoc && node --inspect-brk=9229 ./node_modules/jsdoc/jsdoc.js -R config/jsdoc/api/index.md -c config/jsdoc/api/conf.json -P package.json -d build/apidoc",
"apidoc": "shx rm -rf build/apidoc && jsdoc -R config/jsdoc/api/index.md -c config/jsdoc/api/conf.json -P package.json -d build/apidoc" "apidoc": "shx rm -rf build/apidoc && jsdoc -R config/jsdoc/api/index.md -c config/jsdoc/api/conf.json -P package.json -d build/apidoc"
}, },
"main": "index.js", "main": "index.js",
@@ -35,6 +36,10 @@
"bugs": { "bugs": {
"url": "https://github.com/openlayers/openlayers/issues" "url": "https://github.com/openlayers/openlayers/issues"
}, },
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/openlayers"
},
"dependencies": { "dependencies": {
"elm-pep": "^1.0.4", "elm-pep": "^1.0.4",
"pbf": "3.2.1", "pbf": "3.2.1",
@@ -50,25 +55,26 @@
"@types/pbf": "^3.0.2", "@types/pbf": "^3.0.2",
"@types/topojson-specification": "^1.0.1", "@types/topojson-specification": "^1.0.1",
"babel-loader": "^8.0.5", "babel-loader": "^8.0.5",
"buble": "^0.19.7", "buble": "^0.20.0",
"buble-loader": "^0.5.1", "buble-loader": "^0.5.1",
"chaikin-smooth": "^1.0.4", "chaikin-smooth": "^1.0.4",
"clean-css-cli": "4.3.0", "clean-css-cli": "4.3.0",
"copy-webpack-plugin": "^5.0.4", "copy-webpack-plugin": "^5.0.4",
"coveralls": "3.0.9", "coveralls": "3.0.11",
"eslint": "^6.0.0", "eslint": "^6.0.0",
"eslint-config-openlayers": "^13.0.0", "eslint-config-openlayers": "^13.0.0",
"expect.js": "0.3.1", "expect.js": "0.3.1",
"front-matter": "^3.0.2", "front-matter": "^3.0.2",
"fs-extra": "^8.0.0", "fs-extra": "^9.0.0",
"glob": "^7.1.5", "glob": "^7.1.5",
"globby": "^11.0.0", "globby": "^11.0.0",
"handlebars": "4.7.3", "handlebars": "4.7.4",
"istanbul": "0.4.5", "istanbul": "0.4.5",
"istanbul-instrumenter-loader": "^3.0.1", "istanbul-instrumenter-loader": "^3.0.1",
"jquery": "3.4.1", "jquery": "3.4.1",
"jsdoc": "3.6.3", "jsdoc": "3.6.3",
"jsdoc-plugin-typescript": "^2.0.5", "jsdoc-plugin-typescript": "^2.0.5",
"json-stringify-safe": "^5.0.1",
"karma": "^4.4.1", "karma": "^4.4.1",
"karma-chrome-launcher": "3.1.0", "karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "^2.1.1", "karma-coverage-istanbul-reporter": "^2.1.1",
@@ -77,14 +83,14 @@
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.0-rc.2", "karma-webpack": "^4.0.0-rc.2",
"loglevelnext": "^3.0.1", "loglevelnext": "^3.0.1",
"marked": "0.8.0", "marked": "0.8.2",
"mocha": "7.1.0", "mocha": "7.1.1",
"ol-mapbox-style": "^6.0.0", "ol-mapbox-style": "^6.1.1",
"pixelmatch": "^5.1.0", "pixelmatch": "^5.1.0",
"pngjs": "^3.4.0", "pngjs": "^3.4.0",
"proj4": "2.6.0", "proj4": "2.6.1",
"puppeteer": "~2.1.0", "puppeteer": "~2.1.0",
"rollup": "^1.25.1", "rollup": "^2.1.0",
"rollup-plugin-babel": "^4.3.3", "rollup-plugin-babel": "^4.3.3",
"rollup-plugin-commonjs": "^10.0.0", "rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-node-resolve": "^5.2.0",
@@ -96,10 +102,11 @@
"typescript": "3.5.3", "typescript": "3.5.3",
"url-polyfill": "^1.1.5", "url-polyfill": "^1.1.5",
"walk": "^2.3.9", "walk": "^2.3.9",
"webpack": "4.42.0", "webpack": "4.42.1",
"webpack-cli": "^3.3.2", "webpack-cli": "^3.3.2",
"webpack-dev-middleware": "^3.6.2", "webpack-dev-middleware": "^3.6.2",
"webpack-dev-server": "^3.3.1", "webpack-dev-server": "^3.3.1",
"worker-loader": "^2.0.0",
"yargs": "^15.0.2" "yargs": "^15.0.2"
}, },
"eslintConfig": { "eslintConfig": {

BIN
sponsor-logos/pozi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -37,15 +37,21 @@ import {containsExtent} from './extent.js';
* container as that of the controls (see the `stopEvent` option) you will * container as that of the controls (see the `stopEvent` option) you will
* probably set `insertFirst` to `true` so the overlay is displayed below the * probably set `insertFirst` to `true` so the overlay is displayed below the
* controls. * controls.
* @property {boolean} [autoPan=false] If set to `true` the map is panned when * @property {PanIntoViewOptions|boolean} [autoPan=false] Pan the map when calling
* calling `setPosition`, so that the overlay is entirely visible in the current * `setPosition`, so that the overlay is entirely visible in the current viewport?
* viewport. * If `true` (deprecated), then `autoPanAnimation` and `autoPanMargin` will be
* @property {PanOptions} [autoPanAnimation] The * used to determine the panning parameters; if an object is supplied then other
* animation options used to pan the overlay into view. This animation is only * parameters are ignored.
* used when `autoPan` is enabled. A `duration` and `easing` may be provided to * @property {PanOptions} [autoPanAnimation] The animation options used to pan
* customize the animation. * the overlay into view. This animation is only used when `autoPan` is enabled.
* A `duration` and `easing` may be provided to customize the animation.
* Deprecated and ignored if `autoPan` is supplied as an object.
* @property {number} [autoPanMargin=20] The margin (in pixels) between the * @property {number} [autoPanMargin=20] The margin (in pixels) between the
* overlay and the borders of the map when autopanning. * overlay and the borders of the map when autopanning. Deprecated and ignored
* if `autoPan` is supplied as an object.
* @property {PanIntoViewOptions} [autoPanOptions] The options to use for the
* autoPan. This is only used when `autoPan` is enabled and has preference over
* the individual `autoPanMargin` and `autoPanOptions`.
* @property {string} [className='ol-overlay-container ol-selectable'] CSS class * @property {string} [className='ol-overlay-container ol-selectable'] CSS class
* name. * name.
*/ */
@@ -60,6 +66,12 @@ import {containsExtent} from './extent.js';
* Default is {@link module:ol/easing~inAndOut}. * Default is {@link module:ol/easing~inAndOut}.
*/ */
/**
* @typedef {Object} PanIntoViewOptions
* @property {PanOptions} [animation={}] The animation parameters for the pan
* @property {number} [margin=20] The margin (in pixels) between the
* overlay and the borders of the map when panning into view.
*/
/** /**
* @enum {string} * @enum {string}
@@ -137,38 +149,26 @@ class Overlay extends BaseObject {
options.className : 'ol-overlay-container ' + CLASS_SELECTABLE; options.className : 'ol-overlay-container ' + CLASS_SELECTABLE;
this.element.style.position = 'absolute'; this.element.style.position = 'absolute';
let autoPan = options.autoPan;
if (autoPan && ('object' !== typeof autoPan)) {
autoPan = {
animation: options.autoPanAnimation,
margin: options.autoPanMargin
};
}
/** /**
* @protected * @protected
* @type {boolean} * @type {PanIntoViewOptions|false}
*/ */
this.autoPan = options.autoPan !== undefined ? options.autoPan : false; this.autoPan = /** @type {PanIntoViewOptions} */(autoPan) || false;
/** /**
* @protected * @protected
* @type {PanOptions} * @type {{transform_: string,
*/
this.autoPanAnimation = options.autoPanAnimation || /** @type {PanOptions} */ ({});
/**
* @protected
* @type {number}
*/
this.autoPanMargin = options.autoPanMargin !== undefined ?
options.autoPanMargin : 20;
/**
* @protected
* @type {{bottom_: string,
* left_: string,
* right_: string,
* top_: string,
* visible: boolean}} * visible: boolean}}
*/ */
this.rendered = { this.rendered = {
bottom_: '', transform_: '',
left_: '',
right_: '',
top_: '',
visible: true visible: true
}; };
@@ -300,6 +300,7 @@ class Overlay extends BaseObject {
} else { } else {
container.appendChild(this.element); container.appendChild(this.element);
} }
this.performAutoPan();
} }
} }
@@ -322,9 +323,7 @@ class Overlay extends BaseObject {
*/ */
handlePositionChanged() { handlePositionChanged() {
this.updatePixelPosition(); this.updatePixelPosition();
if (this.get(Property.POSITION) && this.autoPan) { this.performAutoPan();
this.panIntoView();
}
} }
/** /**
@@ -378,14 +377,26 @@ class Overlay extends BaseObject {
} }
/** /**
* Pan the map so that the overlay is entirely visible in the current viewport * Pan the map so that the overlay is entirely visisble in the current viewport
* (if necessary). * (if necessary) using the configured autoPan parameters
* @protected * @protected
*/ */
panIntoView() { performAutoPan() {
if (this.autoPan) {
this.panIntoView(this.autoPan);
}
}
/**
* Pan the map so that the overlay is entirely visible in the current viewport
* (if necessary).
* @param {PanIntoViewOptions|undefined} panIntoViewOptions Options for the pan action
* @api
*/
panIntoView(panIntoViewOptions) {
const map = this.getMap(); const map = this.getMap();
if (!map || !map.getTargetElement()) { if (!map || !map.getTargetElement() || !this.get(Property.POSITION)) {
return; return;
} }
@@ -393,7 +404,7 @@ class Overlay extends BaseObject {
const element = this.getElement(); const element = this.getElement();
const overlayRect = this.getRect(element, [outerWidth(element), outerHeight(element)]); const overlayRect = this.getRect(element, [outerWidth(element), outerHeight(element)]);
const margin = this.autoPanMargin; const myMargin = (panIntoViewOptions.margin === undefined) ? 20 : panIntoViewOptions.margin;
if (!containsExtent(mapRect, overlayRect)) { if (!containsExtent(mapRect, overlayRect)) {
// the overlay is not completely inside the viewport, so pan the map // the overlay is not completely inside the viewport, so pan the map
const offsetLeft = overlayRect[0] - mapRect[0]; const offsetLeft = overlayRect[0] - mapRect[0];
@@ -404,17 +415,17 @@ class Overlay extends BaseObject {
const delta = [0, 0]; const delta = [0, 0];
if (offsetLeft < 0) { if (offsetLeft < 0) {
// move map to the left // move map to the left
delta[0] = offsetLeft - margin; delta[0] = offsetLeft - myMargin;
} else if (offsetRight < 0) { } else if (offsetRight < 0) {
// move map to the right // move map to the right
delta[0] = Math.abs(offsetRight) + margin; delta[0] = Math.abs(offsetRight) + myMargin;
} }
if (offsetTop < 0) { if (offsetTop < 0) {
// move map up // move map up
delta[1] = offsetTop - margin; delta[1] = offsetTop - myMargin;
} else if (offsetBottom < 0) { } else if (offsetBottom < 0) {
// move map down // move map down
delta[1] = Math.abs(offsetBottom) + margin; delta[1] = Math.abs(offsetBottom) + myMargin;
} }
if (delta[0] !== 0 || delta[1] !== 0) { if (delta[0] !== 0 || delta[1] !== 0) {
@@ -425,10 +436,11 @@ class Overlay extends BaseObject {
centerPx[1] + delta[1] centerPx[1] + delta[1]
]; ];
const panOptions = panIntoViewOptions.animation || {};
map.getView().animateInternal({ map.getView().animateInternal({
center: map.getCoordinateFromPixelInternal(newCenterPx), center: map.getCoordinateFromPixelInternal(newCenterPx),
duration: this.autoPanAnimation.duration, duration: panOptions.duration,
easing: this.autoPanAnimation.easing easing: panOptions.easing
}); });
} }
} }
@@ -506,63 +518,34 @@ class Overlay extends BaseObject {
this.setVisible(true); this.setVisible(true);
let offsetX = offset[0]; const x = Math.round(pixel[0] + offset[0]) + 'px';
let offsetY = offset[1]; const y = Math.round(pixel[1] + offset[1]) + 'px';
let posX = '0%';
let posY = '0%';
if (positioning == OverlayPositioning.BOTTOM_RIGHT || if (positioning == OverlayPositioning.BOTTOM_RIGHT ||
positioning == OverlayPositioning.CENTER_RIGHT || positioning == OverlayPositioning.CENTER_RIGHT ||
positioning == OverlayPositioning.TOP_RIGHT) { positioning == OverlayPositioning.TOP_RIGHT) {
if (this.rendered.left_ !== '') { posX = '-100%';
this.rendered.left_ = ''; } else if (positioning == OverlayPositioning.BOTTOM_CENTER ||
style.left = ''; positioning == OverlayPositioning.CENTER_CENTER ||
} positioning == OverlayPositioning.TOP_CENTER) {
const right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px'; posX = '-50%';
if (this.rendered.right_ != right) {
this.rendered.right_ = right;
style.right = right;
}
} else {
if (this.rendered.right_ !== '') {
this.rendered.right_ = '';
style.right = '';
}
if (positioning == OverlayPositioning.BOTTOM_CENTER ||
positioning == OverlayPositioning.CENTER_CENTER ||
positioning == OverlayPositioning.TOP_CENTER) {
offsetX -= this.element.offsetWidth / 2;
}
const left = Math.round(pixel[0] + offsetX) + 'px';
if (this.rendered.left_ != left) {
this.rendered.left_ = left;
style.left = left;
}
} }
if (positioning == OverlayPositioning.BOTTOM_LEFT || if (positioning == OverlayPositioning.BOTTOM_LEFT ||
positioning == OverlayPositioning.BOTTOM_CENTER || positioning == OverlayPositioning.BOTTOM_CENTER ||
positioning == OverlayPositioning.BOTTOM_RIGHT) { positioning == OverlayPositioning.BOTTOM_RIGHT) {
if (this.rendered.top_ !== '') { posY = '-100%';
this.rendered.top_ = ''; } else if (positioning == OverlayPositioning.CENTER_LEFT ||
style.top = ''; positioning == OverlayPositioning.CENTER_CENTER ||
} positioning == OverlayPositioning.CENTER_RIGHT) {
const bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px'; posY = '-50%';
if (this.rendered.bottom_ != bottom) { }
this.rendered.bottom_ = bottom; const transform = `translate(${posX}, ${posY}) translate(${x}, ${y})`;
style.bottom = bottom; if (this.rendered.transform_ != transform) {
} this.rendered.transform_ = transform;
} else { style.transform = transform;
if (this.rendered.bottom_ !== '') { // @ts-ignore IE9
this.rendered.bottom_ = ''; style.msTransform = transform;
style.bottom = '';
}
if (positioning == OverlayPositioning.CENTER_LEFT ||
positioning == OverlayPositioning.CENTER_CENTER ||
positioning == OverlayPositioning.CENTER_RIGHT) {
offsetY -= this.element.offsetHeight / 2;
}
const top = Math.round(pixel[1] + offsetY) + 'px';
if (this.rendered.top_ != top) {
this.rendered.top_ = 'top';
style.top = top;
}
} }
} }

View File

@@ -12,7 +12,7 @@ import MapProperty from './MapProperty.js';
import RenderEventType from './render/EventType.js'; import RenderEventType from './render/EventType.js';
import BaseObject, {getChangeEventType} from './Object.js'; import BaseObject, {getChangeEventType} from './Object.js';
import ObjectEventType from './ObjectEventType.js'; import ObjectEventType from './ObjectEventType.js';
import TileQueue from './TileQueue.js'; import TileQueue, {getTilePriority} from './TileQueue.js';
import View from './View.js'; import View from './View.js';
import ViewHint from './ViewHint.js'; import ViewHint from './ViewHint.js';
import {assert} from './asserts.js'; import {assert} from './asserts.js';
@@ -24,7 +24,6 @@ import {TRUE} from './functions.js';
import {DEVICE_PIXEL_RATIO, IMAGE_DECODE, PASSIVE_EVENT_LISTENERS} from './has.js'; import {DEVICE_PIXEL_RATIO, IMAGE_DECODE, PASSIVE_EVENT_LISTENERS} from './has.js';
import LayerGroup from './layer/Group.js'; import LayerGroup from './layer/Group.js';
import {hasArea} from './size.js'; import {hasArea} from './size.js';
import {DROP} from './structs/PriorityQueue.js';
import {create as createTransform, apply as applyTransform} from './transform.js'; import {create as createTransform, apply as applyTransform} from './transform.js';
import {toUserCoordinate, fromUserCoordinate} from './proj.js'; import {toUserCoordinate, fromUserCoordinate} from './proj.js';
@@ -891,26 +890,7 @@ class PluggableMap extends BaseObject {
* @return {number} Tile priority. * @return {number} Tile priority.
*/ */
getTilePriority(tile, tileSourceKey, tileCenter, tileResolution) { getTilePriority(tile, tileSourceKey, tileCenter, tileResolution) {
// Filter out tiles at higher zoom levels than the current zoom level, or that return getTilePriority(this.frameState_, tile, tileSourceKey, tileCenter, tileResolution);
// are outside the visible extent.
const frameState = this.frameState_;
if (!frameState || !(tileSourceKey in frameState.wantedTiles)) {
return DROP;
}
if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) {
return DROP;
}
// Prioritize the highest zoom level tiles closest to the focus.
// Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
// Within a zoom level, tiles are prioritized by the distance in pixels between
// the center of the tile and the center of the viewport. The factor of 65536
// means that the prioritization should behave as desired for tiles up to
// 65536 * Math.log(2) = 45426 pixels from the focus.
const center = frameState.viewState.center;
const deltaX = tileCenter[0] - center[0];
const deltaY = tileCenter[1] - center[1];
return 65536 * Math.log(tileResolution) +
Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
} }
/** /**

View File

@@ -3,7 +3,7 @@
*/ */
import TileState from './TileState.js'; import TileState from './TileState.js';
import EventType from './events/EventType.js'; import EventType from './events/EventType.js';
import PriorityQueue from './structs/PriorityQueue.js'; import PriorityQueue, {DROP} from './structs/PriorityQueue.js';
/** /**
@@ -119,3 +119,34 @@ class TileQueue extends PriorityQueue {
export default TileQueue; export default TileQueue;
/**
* @param {import('./PluggableMap.js').FrameState} frameState Frame state.
* @param {import("./Tile.js").default} tile Tile.
* @param {string} tileSourceKey Tile source key.
* @param {import("./coordinate.js").Coordinate} tileCenter Tile center.
* @param {number} tileResolution Tile resolution.
* @return {number} Tile priority.
*/
export function getTilePriority(frameState, tile, tileSourceKey, tileCenter, tileResolution) {
// Filter out tiles at higher zoom levels than the current zoom level, or that
// are outside the visible extent.
if (!frameState || !(tileSourceKey in frameState.wantedTiles)) {
return DROP;
}
if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) {
return DROP;
}
// Prioritize the highest zoom level tiles closest to the focus.
// Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
// Within a zoom level, tiles are prioritized by the distance in pixels between
// the center of the tile and the center of the viewport. The factor of 65536
// means that the prioritization should behave as desired for tiles up to
// 65536 * Math.log(2) = 45426 pixels from the focus.
const center = frameState.viewState.center;
const deltaX = tileCenter[0] - center[0];
const deltaY = tileCenter[1] - center[1];
return 65536 * Math.log(tileResolution) +
Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
}

View File

@@ -3,6 +3,7 @@
*/ */
import {modulo} from './math.js'; import {modulo} from './math.js';
import {padNumber} from './string.js'; import {padNumber} from './string.js';
import {getWidth} from './extent.js';
/** /**
@@ -402,3 +403,23 @@ export function toStringHDMS(coordinate, opt_fractionDigits) {
export function toStringXY(coordinate, opt_fractionDigits) { export function toStringXY(coordinate, opt_fractionDigits) {
return format(coordinate, '{x}, {y}', opt_fractionDigits); return format(coordinate, '{x}, {y}', opt_fractionDigits);
} }
/**
* Modifies the provided coordinate in-place to be within the real world
* extent. The lower projection extent boundary is inclusive, the upper one
* exclusive.
*
* @param {Coordinate} coordinate Coordinate.
* @param {import("./proj/Projection.js").default} projection Projection
* @return {Coordinate} The coordinate within the real world extent.
*/
export function wrapX(coordinate, projection) {
const projectionExtent = projection.getExtent();
if (projection.canWrapX() && (coordinate[0] < projectionExtent[0] || coordinate[0] >= projectionExtent[2])) {
const worldWidth = getWidth(projectionExtent);
const worldsAway = Math.floor((coordinate[0] - projectionExtent[0]) / worldWidth);
coordinate[0] -= (worldsAway * worldWidth);
}
return coordinate;
}

View File

@@ -4,9 +4,13 @@
/** /**
* @typedef {Object} FontParameters * @typedef {Object} FontParameters
* @property {Array<string>} families
* @property {string} style * @property {string} style
* @property {string} variant
* @property {string} weight * @property {string} weight
* @property {string} size
* @property {string} lineHeight
* @property {string} family
* @property {Array<string>} families
*/ */
@@ -64,42 +68,52 @@ export const CLASS_CONTROL = 'ol-control';
*/ */
export const CLASS_COLLAPSED = 'ol-collapsed'; export const CLASS_COLLAPSED = 'ol-collapsed';
/**
* From http://stackoverflow.com/questions/10135697/regex-to-parse-any-css-font
* @type {RegExp}
*/
const fontRegEx = new RegExp([
'^\\s*(?=(?:(?:[-a-z]+\\s*){0,2}(italic|oblique))?)',
'(?=(?:(?:[-a-z]+\\s*){0,2}(small-caps))?)',
'(?=(?:(?:[-a-z]+\\s*){0,2}(bold(?:er)?|lighter|[1-9]00 ))?)',
'(?:(?:normal|\\1|\\2|\\3)\\s*){0,3}((?:xx?-)?',
'(?:small|large)|medium|smaller|larger|[\\.\\d]+(?:\\%|in|[cem]m|ex|p[ctx]))',
'(?:\\s*\\/\\s*(normal|[\\.\\d]+(?:\\%|in|[cem]m|ex|p[ctx])?))',
'?\\s*([-,\\"\\\'\\sa-z]+?)\\s*$'
].join(''), 'i');
const fontRegExMatchIndex = [
'style',
'variant',
'weight',
'size',
'lineHeight',
'family'
];
/** /**
* Get the list of font families from a font spec. Note that this doesn't work * Get the list of font families from a font spec. Note that this doesn't work
* for font families that have commas in them. * for font families that have commas in them.
* @param {string} The CSS font property. * @param {string} fontSpec The CSS font property.
* @return {FontParameters} The font families (or null if the input spec is invalid). * @return {FontParameters} The font parameters (or null if the input spec is invalid).
*/ */
export const getFontParameters = (function() { export const getFontParameters = function(fontSpec) {
/** const match = fontSpec.match(fontRegEx);
* @type {CSSStyleDeclaration} if (!match) {
*/ return null;
let style; }
/** const style = /** @type {FontParameters} */ ({
* @type {Object<string, FontParameters>} lineHeight: 'normal',
*/ size: '1.2em',
const cache = {}; style: 'normal',
return function(font) { weight: 'normal',
if (!style) { variant: 'normal'
style = document.createElement('div').style; });
for (let i = 0, ii = fontRegExMatchIndex.length; i < ii; ++i) {
const value = match[i + 1];
if (value !== undefined) {
style[fontRegExMatchIndex[i]] = value;
} }
if (!(font in cache)) { }
style.font = font; style.families = style.family.split(/,\s?/);
const family = style.fontFamily; return style;
const fontWeight = style.fontWeight; };
const fontStyle = style.fontStyle;
style.font = '';
if (!family) {
return null;
}
const families = family.split(/,\s?/);
cache[font] = {
families: families,
weight: fontWeight,
style: fontStyle
};
}
return cache[font];
};
})();

View File

@@ -1,8 +1,11 @@
import {WORKER_OFFSCREEN_CANVAS} from './has.js';
/** /**
* @module ol/dom * @module ol/dom
*/ */
//FIXME Move this function to the canvas module
/** /**
* Create an html canvas element and returns its 2d context. * Create an html canvas element and returns its 2d context.
* @param {number=} opt_width Canvas width. * @param {number=} opt_width Canvas width.
@@ -12,14 +15,18 @@
*/ */
export function createCanvasContext2D(opt_width, opt_height, opt_canvasPool) { export function createCanvasContext2D(opt_width, opt_height, opt_canvasPool) {
const canvas = opt_canvasPool && opt_canvasPool.length ? const canvas = opt_canvasPool && opt_canvasPool.length ?
opt_canvasPool.shift() : document.createElement('canvas'); opt_canvasPool.shift() :
WORKER_OFFSCREEN_CANVAS ?
new OffscreenCanvas(opt_width || 300, opt_height || 300) :
document.createElement('canvas');
if (opt_width) { if (opt_width) {
canvas.width = opt_width; canvas.width = opt_width;
} }
if (opt_height) { if (opt_height) {
canvas.height = opt_height; canvas.height = opt_height;
} }
return canvas.getContext('2d'); //FIXME Allow OffscreenCanvasRenderingContext2D as return type
return /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
} }

View File

@@ -295,6 +295,18 @@ export function equals(extent1, extent2) {
extent1[1] == extent2[1] && extent1[3] == extent2[3]; extent1[1] == extent2[1] && extent1[3] == extent2[3];
} }
/**
* Determine if two extents are approximately equivalent.
* @param {Extent} extent1 Extent 1.
* @param {Extent} extent2 Extent 2.
* @param {number} tolerance Tolerance in extent coordinate units.
* @return {boolean} The two extents differ by less than the tolerance.
*/
export function approximatelyEquals(extent1, extent2, tolerance) {
return Math.abs(extent1[0] - extent2[0]) < tolerance && Math.abs(extent1[2] - extent2[2]) < tolerance &&
Math.abs(extent1[1] - extent2[1]) < tolerance && Math.abs(extent1[3] - extent2[3]) < tolerance;
}
/** /**
* Modify an extent to include another extent. * Modify an extent to include another extent.
@@ -813,3 +825,25 @@ export function applyTransform(extent, transformFn, opt_extent, opt_stops) {
} }
return _boundingExtentXYs(xs, ys, opt_extent); return _boundingExtentXYs(xs, ys, opt_extent);
} }
/**
* Modifies the provided extent in-place to be within the real world
* extent.
*
* @param {Extent} extent Extent.
* @param {import("./proj/Projection.js").default} projection Projection
* @return {Extent} The extent within the real world extent.
*/
export function wrapX(extent, projection) {
const projectionExtent = projection.getExtent();
const center = getCenter(extent);
if (projection.canWrapX() && (center[0] < projectionExtent[0] || center[0] >= projectionExtent[2])) {
const worldWidth = getWidth(projectionExtent);
const worldsAway = Math.floor((center[0] - projectionExtent[0]) / worldWidth);
const offset = (worldsAway * worldWidth);
extent[0] -= offset;
extent[2] -= offset;
}
return extent;
}

View File

@@ -918,11 +918,7 @@ function createNameStyleFunction(foundStyle, name) {
const nameStyle = new Style({ const nameStyle = new Style({
image: imageStyle, image: imageStyle,
text: textStyle, text: textStyle
// although nameStyle will be used only for Point geometries
// fill and stroke are included to assist writing of MultiGeometry styles
fill: foundStyle.getFill(),
stroke: foundStyle.getStroke()
}); });
return nameStyle; return nameStyle;
} }
@@ -953,7 +949,7 @@ function createFeatureStyleFunction(style, styleUrl, defaultStyle, sharedStyles,
if (geometry) { if (geometry) {
const type = geometry.getType(); const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) { if (type === GeometryType.GEOMETRY_COLLECTION) {
multiGeometryPoints = geometry.getGeometriesArray().filter(function(geometry) { multiGeometryPoints = geometry.getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType(); const type = geometry.getType();
return type === GeometryType.POINT || type === GeometryType.MULTI_POINT; return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
}); });
@@ -1171,6 +1167,7 @@ function readStyleMapValue(node, objectStack) {
const ICON_STYLE_PARSERS = makeStructureNS( const ICON_STYLE_PARSERS = makeStructureNS(
NAMESPACE_URIS, { NAMESPACE_URIS, {
'Icon': makeObjectPropertySetter(readIcon), 'Icon': makeObjectPropertySetter(readIcon),
'color': makeObjectPropertySetter(readColor),
'heading': makeObjectPropertySetter(readDecimal), 'heading': makeObjectPropertySetter(readDecimal),
'hotSpot': makeObjectPropertySetter(readVec2), 'hotSpot': makeObjectPropertySetter(readVec2),
'scale': makeObjectPropertySetter(readScale) 'scale': makeObjectPropertySetter(readScale)
@@ -1252,6 +1249,9 @@ function iconStyleParser(node, objectStack) {
let scale = /** @type {number|undefined} */ let scale = /** @type {number|undefined} */
(object['scale']); (object['scale']);
const color = /** @type {Array<number>|undefined} */
(object['color']);
if (drawIcon) { if (drawIcon) {
if (src == DEFAULT_IMAGE_STYLE_SRC) { if (src == DEFAULT_IMAGE_STYLE_SRC) {
size = DEFAULT_IMAGE_STYLE_SIZE; size = DEFAULT_IMAGE_STYLE_SIZE;
@@ -1271,7 +1271,8 @@ function iconStyleParser(node, objectStack) {
rotation: rotation, rotation: rotation,
scale: scale, scale: scale,
size: size, size: size,
src: src src: src,
color: color
}); });
styleObject['imageStyle'] = imageStyle; styleObject['imageStyle'] = imageStyle;
} else { } else {
@@ -1806,7 +1807,7 @@ function readStyle(node, objectStack) {
const type = geometry.getType(); const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) { if (type === GeometryType.GEOMETRY_COLLECTION) {
return new GeometryCollection( return new GeometryCollection(
geometry.getGeometriesArray().filter(function(geometry) { geometry.getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType(); const type = geometry.getType();
return type !== GeometryType.POLYGON && type !== GeometryType.MULTI_POLYGON; return type !== GeometryType.POLYGON && type !== GeometryType.MULTI_POLYGON;
}) })
@@ -1827,7 +1828,7 @@ function readStyle(node, objectStack) {
const type = geometry.getType(); const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) { if (type === GeometryType.GEOMETRY_COLLECTION) {
return new GeometryCollection( return new GeometryCollection(
geometry.getGeometriesArray().filter(function(geometry) { geometry.getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType(); const type = geometry.getType();
return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON; return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
}) })
@@ -2447,7 +2448,7 @@ function writeIcon(node, icon, objectStack) {
// @ts-ignore // @ts-ignore
const ICON_STYLE_SEQUENCE = makeStructureNS( const ICON_STYLE_SEQUENCE = makeStructureNS(
NAMESPACE_URIS, [ NAMESPACE_URIS, [
'scale', 'heading', 'Icon', 'hotSpot' 'scale', 'heading', 'Icon', 'color', 'hotSpot'
]); ]);
@@ -2459,6 +2460,7 @@ const ICON_STYLE_SEQUENCE = makeStructureNS(
const ICON_STYLE_SERIALIZERS = makeStructureNS( const ICON_STYLE_SERIALIZERS = makeStructureNS(
NAMESPACE_URIS, { NAMESPACE_URIS, {
'Icon': makeChildAppender(writeIcon), 'Icon': makeChildAppender(writeIcon),
'color': makeChildAppender(writeColorTextNode),
'heading': makeChildAppender(writeDecimalTextNode), 'heading': makeChildAppender(writeDecimalTextNode),
'hotSpot': makeChildAppender(writeVec2), 'hotSpot': makeChildAppender(writeVec2),
'scale': makeChildAppender(writeScaleTextNode) 'scale': makeChildAppender(writeScaleTextNode)
@@ -2514,6 +2516,11 @@ function writeIconStyle(node, style, objectStack) {
properties['heading'] = rotation; // 0-360 properties['heading'] = rotation; // 0-360
} }
const color = style.getColor();
if (color) {
properties['color'] = color;
}
const parentNode = objectStack[objectStack.length - 1].node; const parentNode = objectStack[objectStack.length - 1].node;
const orderedKeys = ICON_STYLE_SEQUENCE[parentNode.namespaceURI]; const orderedKeys = ICON_STYLE_SEQUENCE[parentNode.namespaceURI];
const values = makeSequence(properties, orderedKeys); const values = makeSequence(properties, orderedKeys);
@@ -2703,20 +2710,35 @@ function writeMultiGeometry(node, geometry, objectStack) {
const context = {node: node}; const context = {node: node};
const type = geometry.getType(); const type = geometry.getType();
/** @type {Array<import("../geom/Geometry.js").default>} */ /** @type {Array<import("../geom/Geometry.js").default>} */
let geometries; let geometries = [];
/** @type {function(*, Array<*>, string=): (Node|undefined)} */ /** @type {function(*, Array<*>, string=): (Node|undefined)} */
let factory; let factory;
if (type == GeometryType.GEOMETRY_COLLECTION) { if (type === GeometryType.GEOMETRY_COLLECTION) {
geometries = /** @type {GeometryCollection} */ (geometry).getGeometries(); /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().forEach(function(geometry) {
const type = geometry.getType();
if (type === GeometryType.MULTI_POINT) {
geometries = geometries.concat(/** @type {MultiPoint} */ (geometry).getPoints());
} else if (type === GeometryType.MULTI_LINE_STRING) {
geometries = geometries.concat(/** @type {MultiLineString} */ (geometry).getLineStrings());
} else if (type === GeometryType.MULTI_POLYGON) {
geometries = geometries.concat(/** @type {MultiPolygon} */ (geometry).getPolygons());
} else if (type === GeometryType.POINT
|| type === GeometryType.LINE_STRING
|| type === GeometryType.POLYGON) {
geometries.push(geometry);
} else {
assert(false, 39); // Unknown geometry type
}
});
factory = GEOMETRY_NODE_FACTORY; factory = GEOMETRY_NODE_FACTORY;
} else if (type == GeometryType.MULTI_POINT) { } else if (type === GeometryType.MULTI_POINT) {
geometries = /** @type {MultiPoint} */ (geometry).getPoints(); geometries = /** @type {MultiPoint} */ (geometry).getPoints();
factory = POINT_NODE_FACTORY; factory = POINT_NODE_FACTORY;
} else if (type == GeometryType.MULTI_LINE_STRING) { } else if (type === GeometryType.MULTI_LINE_STRING) {
geometries = geometries =
(/** @type {MultiLineString} */ (geometry)).getLineStrings(); (/** @type {MultiLineString} */ (geometry)).getLineStrings();
factory = LINE_STRING_NODE_FACTORY; factory = LINE_STRING_NODE_FACTORY;
} else if (type == GeometryType.MULTI_POLYGON) { } else if (type === GeometryType.MULTI_POLYGON) {
geometries = geometries =
(/** @type {MultiPolygon} */ (geometry)).getPolygons(); (/** @type {MultiPolygon} */ (geometry)).getPolygons();
factory = POLYGON_NODE_FACTORY; factory = POLYGON_NODE_FACTORY;
@@ -2831,13 +2853,61 @@ function writePlacemark(node, feature, objectStack) {
// resolution-independent here // resolution-independent here
const styles = styleFunction(feature, 0); const styles = styleFunction(feature, 0);
if (styles) { if (styles) {
const style = Array.isArray(styles) ? styles[0] : styles; const styleArray = Array.isArray(styles) ? styles : [styles];
if (this.writeStyles_) { let pointStyles = styleArray;
properties['Style'] = style; if (feature.getGeometry()) {
pointStyles = styleArray.filter(function(style) {
const geometry = style.getGeometryFunction()(feature);
if (geometry) {
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
return /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType();
return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
}).length;
}
return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
}
});
} }
const textStyle = style.getText(); if (this.writeStyles_) {
if (textStyle) { let lineStyles = styleArray;
properties['name'] = textStyle.getText(); let polyStyles = styleArray;
if (feature.getGeometry()) {
lineStyles = styleArray.filter(function(style) {
const geometry = style.getGeometryFunction()(feature);
if (geometry) {
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
return /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType();
return type === GeometryType.LINE_STRING || type === GeometryType.MULTI_LINE_STRING;
}).length;
}
return type === GeometryType.LINE_STRING || type === GeometryType.MULTI_LINE_STRING;
}
});
polyStyles = styleArray.filter(function(style) {
const geometry = style.getGeometryFunction()(feature);
if (geometry) {
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
return /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType();
return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
}).length;
}
return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
}
});
}
properties['Style'] = {pointStyles: pointStyles, lineStyles: lineStyles, polyStyles: polyStyles};
}
if (pointStyles.length && properties['name'] === undefined) {
const textStyle = pointStyles[0].getText();
if (textStyle) {
properties['name'] = textStyle.getText();
}
} }
} }
} }
@@ -2913,6 +2983,17 @@ function writePrimitiveGeometry(node, geometry, objectStack) {
} }
/**
* @const
* @type {Object<string, Array<string>>}
*/
// @ts-ignore
const POLY_STYLE_SEQUENCE = makeStructureNS(
NAMESPACE_URIS, [
'color', 'fill', 'outline'
]);
/** /**
* @const * @const
* @type {Object<string, Object<string, import("../xml.js").Serializer>>} * @type {Object<string, Object<string, import("../xml.js").Serializer>>}
@@ -2972,27 +3053,31 @@ function writePolygon(node, polygon, objectStack) {
// @ts-ignore // @ts-ignore
const POLY_STYLE_SERIALIZERS = makeStructureNS( const POLY_STYLE_SERIALIZERS = makeStructureNS(
NAMESPACE_URIS, { NAMESPACE_URIS, {
'color': makeChildAppender(writeColorTextNode) 'color': makeChildAppender(writeColorTextNode),
'fill': makeChildAppender(writeBooleanTextNode),
'outline': makeChildAppender(writeBooleanTextNode)
}); });
/**
* A factory for creating coordinates nodes.
* @const
* @type {function(*, Array<*>, string=): (Node|undefined)}
*/
const COLOR_NODE_FACTORY = makeSimpleNodeFactory('color');
/** /**
* @param {Node} node Node. * @param {Node} node Node.
* @param {Fill} style Style. * @param {Style} style Style.
* @param {Array<*>} objectStack Object stack. * @param {Array<*>} objectStack Object stack.
*/ */
function writePolyStyle(node, style, objectStack) { function writePolyStyle(node, style, objectStack) {
const /** @type {import("../xml.js").NodeStackItem} */ context = {node: node}; const /** @type {import("../xml.js").NodeStackItem} */ context = {node: node};
const fill = style.getFill();
const stroke = style.getStroke();
const properties = {
'color': fill ? fill.getColor() : undefined,
'fill': fill ? undefined : false,
'outline': stroke ? undefined : false
};
const parentNode = objectStack[objectStack.length - 1].node;
const orderedKeys = POLY_STYLE_SEQUENCE[parentNode.namespaceURI];
const values = makeSequence(properties, orderedKeys);
pushSerializeAndPop(context, POLY_STYLE_SERIALIZERS, pushSerializeAndPop(context, POLY_STYLE_SERIALIZERS,
COLOR_NODE_FACTORY, [style.getColor()], objectStack); OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
} }
@@ -3034,27 +3119,34 @@ const STYLE_SERIALIZERS = makeStructureNS(
/** /**
* @param {Node} node Node. * @param {Node} node Node.
* @param {Style} style Style. * @param {Object<string, Array<Style>>} styles Styles.
* @param {Array<*>} objectStack Object stack. * @param {Array<*>} objectStack Object stack.
*/ */
function writeStyle(node, style, objectStack) { function writeStyle(node, styles, objectStack) {
const /** @type {import("../xml.js").NodeStackItem} */ context = {node: node}; const /** @type {import("../xml.js").NodeStackItem} */ context = {node: node};
const properties = {}; const properties = {};
const fillStyle = style.getFill(); if (styles.pointStyles.length) {
const strokeStyle = style.getStroke(); const textStyle = styles.pointStyles[0].getText();
const imageStyle = style.getImage(); if (textStyle) {
const textStyle = style.getText(); properties['LabelStyle'] = textStyle;
if (imageStyle && typeof /** @type {?} */ (imageStyle).getSrc === 'function') { }
properties['IconStyle'] = imageStyle; const imageStyle = styles.pointStyles[0].getImage();
if (imageStyle && typeof /** @type {?} */ (imageStyle).getSrc === 'function') {
properties['IconStyle'] = imageStyle;
}
} }
if (textStyle) { if (styles.lineStyles.length) {
properties['LabelStyle'] = textStyle; const strokeStyle = styles.lineStyles[0].getStroke();
if (strokeStyle) {
properties['LineStyle'] = strokeStyle;
}
} }
if (strokeStyle) { if (styles.polyStyles.length) {
properties['LineStyle'] = strokeStyle; const strokeStyle = styles.polyStyles[0].getStroke();
} if (strokeStyle && !properties['LineStyle']) {
if (fillStyle) { properties['LineStyle'] = strokeStyle;
properties['PolyStyle'] = fillStyle; }
properties['PolyStyle'] = styles.polyStyles[0];
} }
const parentNode = objectStack[objectStack.length - 1].node; const parentNode = objectStack[objectStack.length - 1].node;
const orderedKeys = STYLE_SEQUENCE[parentNode.namespaceURI]; const orderedKeys = STYLE_SEQUENCE[parentNode.namespaceURI];

View File

@@ -126,6 +126,23 @@ class GeometryCollection extends Geometry {
return this.geometries_; return this.geometries_;
} }
/**
* @return {Array<Geometry>} Geometries.
*/
getGeometriesArrayRecursive() {
/** @type {Array<Geometry>} */
let geometriesArray = [];
const geometries = this.geometries_;
for (let i = 0, ii = geometries.length; i < ii; ++i) {
if (geometries[i].getType() === this.getType()) {
geometriesArray = geometriesArray.concat(/** @type {GeometryCollection} */ (geometries[i]).getGeometriesArrayRecursive());
} else {
geometriesArray.push(geometries[i]);
}
}
return geometriesArray;
}
/** /**
* @inheritDoc * @inheritDoc
*/ */

View File

@@ -37,7 +37,15 @@ export const MAC = ua.indexOf('macintosh') !== -1;
* @type {number} * @type {number}
* @api * @api
*/ */
export const DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1; export const DEVICE_PIXEL_RATIO = typeof devicePixelRatio !== 'undefined' ? devicePixelRatio : 1;
/**
* The execution context is a worker with OffscreenCanvas available.
* @const
* @type {boolean}
*/
export const WORKER_OFFSCREEN_CANVAS = typeof WorkerGlobalScope !== 'undefined' && typeof OffscreenCanvas !== 'undefined' &&
self instanceof WorkerGlobalScope; //eslint-disable-line
/** /**
* Image.prototype.decode() is supported. * Image.prototype.decode() is supported.

View File

@@ -14,13 +14,14 @@ import {always, primaryAction, altKeyOnly, singleClick} from '../events/conditio
import {boundingExtent, buffer as bufferExtent, createOrUpdateFromCoordinate as createExtent} from '../extent.js'; import {boundingExtent, buffer as bufferExtent, createOrUpdateFromCoordinate as createExtent} from '../extent.js';
import GeometryType from '../geom/GeometryType.js'; import GeometryType from '../geom/GeometryType.js';
import Point from '../geom/Point.js'; import Point from '../geom/Point.js';
import {fromCircle} from '../geom/Polygon.js';
import PointerInteraction from './Pointer.js'; import PointerInteraction from './Pointer.js';
import VectorLayer from '../layer/Vector.js'; import VectorLayer from '../layer/Vector.js';
import VectorSource from '../source/Vector.js'; import VectorSource from '../source/Vector.js';
import VectorEventType from '../source/VectorEventType.js'; import VectorEventType from '../source/VectorEventType.js';
import RBush from '../structs/RBush.js'; import RBush from '../structs/RBush.js';
import {createEditingStyle} from '../style/Style.js'; import {createEditingStyle} from '../style/Style.js';
import {fromUserExtent, toUserExtent, fromUserCoordinate, toUserCoordinate} from '../proj.js'; import {getUserProjection, fromUserExtent, toUserExtent, fromUserCoordinate, toUserCoordinate} from '../proj.js';
/** /**
@@ -657,7 +658,14 @@ class Modify extends PointerInteraction {
centerSegmentData.featureSegments = featureSegments; centerSegmentData.featureSegments = featureSegments;
circumferenceSegmentData.featureSegments = featureSegments; circumferenceSegmentData.featureSegments = featureSegments;
this.rBush_.insert(createExtent(coordinates), centerSegmentData); this.rBush_.insert(createExtent(coordinates), centerSegmentData);
this.rBush_.insert(geometry.getExtent(), circumferenceSegmentData); let circleGeometry = /** @type {import("../geom/Geometry.js").default} */ (geometry);
const userProjection = getUserProjection();
if (userProjection && this.getMap()) {
const projection = this.getMap().getView().getProjection();
circleGeometry = circleGeometry.clone().transform(userProjection, projection);
circleGeometry = fromCircle(/** @type {import("../geom/Circle.js").default} */ (circleGeometry)).transform(projection, userProjection);
}
this.rBush_.insert(circleGeometry.getExtent(), circumferenceSegmentData);
} }
/** /**
@@ -785,7 +793,16 @@ class Modify extends PointerInteraction {
this.changingFeature_ = false; this.changingFeature_ = false;
} else { // We're dragging the circle's circumference: } else { // We're dragging the circle's circumference:
this.changingFeature_ = true; this.changingFeature_ = true;
geometry.setRadius(coordinateDistance(geometry.getCenter(), vertex)); const projection = evt.map.getView().getProjection();
let radius = coordinateDistance(fromUserCoordinate(geometry.getCenter(), projection),
fromUserCoordinate(vertex, projection));
const userProjection = getUserProjection();
if (userProjection) {
const circleGeometry = geometry.clone().transform(userProjection, projection);
circleGeometry.setRadius(radius);
radius = circleGeometry.transform(projection, userProjection).getRadius();
}
geometry.setRadius(radius);
this.changingFeature_ = false; this.changingFeature_ = false;
} }
break; break;
@@ -898,7 +915,14 @@ class Modify extends PointerInteraction {
circumferenceSegmentData.segment[0] = coordinates; circumferenceSegmentData.segment[0] = coordinates;
circumferenceSegmentData.segment[1] = coordinates; circumferenceSegmentData.segment[1] = coordinates;
this.rBush_.update(createExtent(coordinates), centerSegmentData); this.rBush_.update(createExtent(coordinates), centerSegmentData);
this.rBush_.update(geometry.getExtent(), circumferenceSegmentData); let circleGeometry = geometry;
const userProjection = getUserProjection();
if (userProjection) {
const projection = evt.map.getView().getProjection();
circleGeometry = circleGeometry.clone().transform(userProjection, projection);
circleGeometry = fromCircle(circleGeometry).transform(projection, userProjection);
}
this.rBush_.update(circleGeometry.getExtent(), circumferenceSegmentData);
} else { } else {
this.rBush_.update(boundingExtent(segmentData.segment), segmentData); this.rBush_.update(boundingExtent(segmentData.segment), segmentData);
} }
@@ -1249,11 +1273,15 @@ function projectedDistanceToSegmentDataSquared(pointCoordinates, segmentData, pr
const geometry = segmentData.geometry; const geometry = segmentData.geometry;
if (geometry.getType() === GeometryType.CIRCLE) { if (geometry.getType() === GeometryType.CIRCLE) {
const circleGeometry = /** @type {import("../geom/Circle.js").default} */ (geometry); let circleGeometry = /** @type {import("../geom/Circle.js").default} */ (geometry);
if (segmentData.index === CIRCLE_CIRCUMFERENCE_INDEX) { if (segmentData.index === CIRCLE_CIRCUMFERENCE_INDEX) {
const userProjection = getUserProjection();
if (userProjection) {
circleGeometry = /** @type {import("../geom/Circle.js").default} */ (circleGeometry.clone().transform(userProjection, projection));
}
const distanceToCenterSquared = const distanceToCenterSquared =
squaredCoordinateDistance(circleGeometry.getCenter(), pointCoordinates); squaredCoordinateDistance(circleGeometry.getCenter(), fromUserCoordinate(pointCoordinates, projection));
const distanceToCircumference = const distanceToCircumference =
Math.sqrt(distanceToCenterSquared) - circleGeometry.getRadius(); Math.sqrt(distanceToCenterSquared) - circleGeometry.getRadius();
return distanceToCircumference * distanceToCircumference; return distanceToCircumference * distanceToCircumference;
@@ -1280,7 +1308,12 @@ function closestOnSegmentData(pointCoordinates, segmentData, projection) {
const geometry = segmentData.geometry; const geometry = segmentData.geometry;
if (geometry.getType() === GeometryType.CIRCLE && segmentData.index === CIRCLE_CIRCUMFERENCE_INDEX) { if (geometry.getType() === GeometryType.CIRCLE && segmentData.index === CIRCLE_CIRCUMFERENCE_INDEX) {
return geometry.getClosestPoint(pointCoordinates); let circleGeometry = /** @type {import("../geom/Circle.js").default} */ (geometry);
const userProjection = getUserProjection();
if (userProjection) {
circleGeometry = /** @type {import("../geom/Circle.js").default} */ (circleGeometry.clone().transform(userProjection, projection));
}
return toUserCoordinate(circleGeometry.getClosestPoint(fromUserCoordinate(pointCoordinates, projection)), projection);
} }
const coordinate = fromUserCoordinate(pointCoordinates, projection); const coordinate = fromUserCoordinate(pointCoordinates, projection);
tempSegment[0] = fromUserCoordinate(segmentData.segment[0], projection); tempSegment[0] = fromUserCoordinate(segmentData.segment[0], projection);

View File

@@ -167,6 +167,16 @@ class Select extends Interaction {
const options = opt_options ? opt_options : {}; const options = opt_options ? opt_options : {};
/**
* @private
*/
this.boundAddFeature_ = this.addFeature_.bind(this);
/**
* @private
*/
this.boundRemoveFeature_ = this.removeFeature_.bind(this);
/** /**
* @private * @private
* @type {import("../events/condition.js").Condition} * @type {import("../events/condition.js").Condition}
@@ -249,10 +259,6 @@ class Select extends Interaction {
* @type {Object<string, import("../layer/Layer.js").default>} * @type {Object<string, import("../layer/Layer.js").default>}
*/ */
this.featureLayerAssociation_ = {}; this.featureLayerAssociation_ = {};
const features = this.getFeatures();
features.addEventListener(CollectionEventType.ADD, this.addFeature_.bind(this));
features.addEventListener(CollectionEventType.REMOVE, this.removeFeature_.bind(this));
} }
/** /**
@@ -320,8 +326,16 @@ class Select extends Interaction {
this.features_.forEach(this.restorePreviousStyle_.bind(this)); this.features_.forEach(this.restorePreviousStyle_.bind(this));
} }
super.setMap(map); super.setMap(map);
if (map && this.style_) { if (map) {
this.features_.forEach(this.applySelectedStyle_.bind(this)); this.features_.addEventListener(CollectionEventType.ADD, this.boundAddFeature_);
this.features_.addEventListener(CollectionEventType.REMOVE, this.boundRemoveFeature_);
if (this.style_) {
this.features_.forEach(this.applySelectedStyle_.bind(this));
}
} else {
this.features_.removeEventListener(CollectionEventType.ADD, this.boundAddFeature_);
this.features_.removeEventListener(CollectionEventType.REMOVE, this.boundRemoveFeature_);
} }
} }

View File

@@ -14,7 +14,7 @@ import PointerInteraction from './Pointer.js';
import {getValues} from '../obj.js'; import {getValues} from '../obj.js';
import VectorEventType from '../source/VectorEventType.js'; import VectorEventType from '../source/VectorEventType.js';
import RBush from '../structs/RBush.js'; import RBush from '../structs/RBush.js';
import {fromUserCoordinate, toUserCoordinate} from '../proj.js'; import {getUserProjection, fromUserCoordinate, toUserCoordinate} from '../proj.js';
/** /**
@@ -431,8 +431,13 @@ class Snap extends PointerInteraction {
} else if (this.edge_) { } else if (this.edge_) {
const isCircle = closestSegmentData.feature.getGeometry().getType() === GeometryType.CIRCLE; const isCircle = closestSegmentData.feature.getGeometry().getType() === GeometryType.CIRCLE;
if (isCircle) { if (isCircle) {
vertex = closestOnCircle(pixelCoordinate, let circleGeometry = closestSegmentData.feature.getGeometry();
/** @type {import("../geom/Circle.js").default} */ (closestSegmentData.feature.getGeometry())); const userProjection = getUserProjection();
if (userProjection) {
circleGeometry = circleGeometry.clone().transform(userProjection, projection);
}
vertex = toUserCoordinate(closestOnCircle(projectedCoordinate,
/** @type {import("../geom/Circle.js").default} */ (circleGeometry)), projection);
} else { } else {
tempSegment[0] = fromUserCoordinate(closestSegment[0], projection); tempSegment[0] = fromUserCoordinate(closestSegment[0], projection);
tempSegment[1] = fromUserCoordinate(closestSegment[1], projection); tempSegment[1] = fromUserCoordinate(closestSegment[1], projection);
@@ -482,7 +487,16 @@ class Snap extends PointerInteraction {
* @private * @private
*/ */
writeCircleGeometry_(feature, geometry) { writeCircleGeometry_(feature, geometry) {
const polygon = fromCircle(geometry); const projection = this.getMap().getView().getProjection();
let circleGeometry = geometry;
const userProjection = getUserProjection();
if (userProjection) {
circleGeometry = /** @type {import("../geom/Circle.js").default} */ (circleGeometry.clone().transform(userProjection, projection));
}
const polygon = fromCircle(circleGeometry);
if (userProjection) {
polygon.transform(projection, userProjection);
}
const coordinates = polygon.getCoordinates()[0]; const coordinates = polygon.getCoordinates()[0];
for (let i = 0, ii = coordinates.length - 1; i < ii; ++i) { for (let i = 0, ii = coordinates.length - 1; i < ii; ++i) {
const segment = coordinates.slice(i, i + 2); const segment = coordinates.slice(i, i + 2);

View File

@@ -17,13 +17,16 @@ import {
import { import {
applyTransform, applyTransform,
containsCoordinate, containsCoordinate,
containsExtent,
equals, equals,
approximatelyEquals,
getCenter, getCenter,
getHeight, getHeight,
getIntersection, getIntersection,
getWidth, getWidth,
intersects, intersects,
isEmpty isEmpty,
wrapX as wrapExtentX
} from '../extent.js'; } from '../extent.js';
import {clamp} from '../math.js'; import {clamp} from '../math.js';
import Style from '../style/Style.js'; import Style from '../style/Style.js';
@@ -478,11 +481,21 @@ class Graticule extends VectorLayer {
* @return {Array<import("../extent.js").Extent>} Extents. * @return {Array<import("../extent.js").Extent>} Extents.
*/ */
strategyFunction(extent, resolution) { strategyFunction(extent, resolution) {
if (this.loadedExtent_ && !equals(this.loadedExtent_, extent)) { // extents may be passed in different worlds, to avoid endless loop we use only one
// we should not keep track of loaded extents let realWorldExtent = extent.slice();
this.getSource().removeLoadedExtent(this.loadedExtent_); if (this.projection_ && this.getSource().getWrapX()) {
wrapExtentX(realWorldExtent, this.projection_);
} }
return [extent]; if (this.loadedExtent_) {
if (approximatelyEquals(this.loadedExtent_, realWorldExtent, resolution)) {
// make sure result is exactly equal to previous extent
realWorldExtent = this.loadedExtent_.slice();
} else {
// we should not keep track of loaded extents
this.getSource().removeLoadedExtent(this.loadedExtent_);
}
}
return [realWorldExtent];
} }
/** /**
@@ -622,9 +635,9 @@ class Graticule extends VectorLayer {
drawLabels_(event) { drawLabels_(event) {
const rotation = event.frameState.viewState.rotation; const rotation = event.frameState.viewState.rotation;
const extent = event.frameState.extent; const extent = event.frameState.extent;
let rotationCenter, rotationExtent; const rotationCenter = getCenter(extent);
let rotationExtent = extent;
if (rotation) { if (rotation) {
rotationCenter = getCenter(extent);
const width = getWidth(extent); const width = getWidth(extent);
const height = getHeight(extent); const height = getHeight(extent);
const cr = Math.abs(Math.cos(rotation)); const cr = Math.abs(Math.cos(rotation));
@@ -637,42 +650,60 @@ class Graticule extends VectorLayer {
]; ];
} }
const vectorContext = getVectorContext(event); let startWorld = 0;
let poolIndex = this.meridians_.length + this.parallels_.length; let endWorld = 0;
let feature, index, l, textPoint; let labelsAtStart = this.latLabelPosition_ < 0.5;
const projectionExtent = this.projection_.getExtent();
if (this.meridiansLabels_) { const worldWidth = getWidth(projectionExtent);
for (index = 0, l = this.meridiansLabels_.length; index < l; ++index) { if (this.getSource().getWrapX() && this.projection_.canWrapX() && !containsExtent(projectionExtent, extent)) {
const lineString = this.meridians_[index]; startWorld = Math.floor((extent[0] - projectionExtent[0]) / worldWidth);
if (!rotation) { endWorld = Math.ceil((extent[2] - projectionExtent[2]) / worldWidth);
textPoint = this.getMeridianPoint_(lineString, extent, index); const inverted = Math.abs(rotation) > Math.PI / 2;
} else { labelsAtStart = labelsAtStart !== inverted;
const clone = lineString.clone();
clone.rotate(-rotation, rotationCenter);
textPoint = this.getMeridianPoint_(clone, rotationExtent, index);
textPoint.rotate(rotation, rotationCenter);
}
feature = this.featurePool_[poolIndex++];
feature.setGeometry(textPoint);
feature.set('graticule_label', this.meridiansLabels_[index].text);
vectorContext.drawFeature(feature, this.lonLabelStyle_(feature));
}
} }
if (this.parallelsLabels_) { const vectorContext = getVectorContext(event);
for (index = 0, l = this.parallels_.length; index < l; ++index) {
const lineString = this.parallels_[index]; for (let world = startWorld; world <= endWorld; ++world) {
if (!rotation) { let poolIndex = this.meridians_.length + this.parallels_.length;
textPoint = this.getParallelPoint_(lineString, extent, index); let feature, index, l, textPoint;
} else {
const clone = lineString.clone(); if (this.meridiansLabels_) {
clone.rotate(-rotation, rotationCenter); for (index = 0, l = this.meridiansLabels_.length; index < l; ++index) {
textPoint = this.getParallelPoint_(clone, rotationExtent, index); const lineString = this.meridians_[index];
textPoint.rotate(rotation, rotationCenter); if (!rotation && world === 0) {
textPoint = this.getMeridianPoint_(lineString, extent, index);
} else {
const clone = lineString.clone();
clone.translate(world * worldWidth, 0);
clone.rotate(-rotation, rotationCenter);
textPoint = this.getMeridianPoint_(clone, rotationExtent, index);
textPoint.rotate(rotation, rotationCenter);
}
feature = this.featurePool_[poolIndex++];
feature.setGeometry(textPoint);
feature.set('graticule_label', this.meridiansLabels_[index].text);
vectorContext.drawFeature(feature, this.lonLabelStyle_(feature));
}
}
if (this.parallelsLabels_) {
if (world === startWorld && labelsAtStart || world === endWorld && !labelsAtStart) {
for (index = 0, l = this.parallels_.length; index < l; ++index) {
const lineString = this.parallels_[index];
if (!rotation && world === 0) {
textPoint = this.getParallelPoint_(lineString, extent, index);
} else {
const clone = lineString.clone();
clone.translate(world * worldWidth, 0);
clone.rotate(-rotation, rotationCenter);
textPoint = this.getParallelPoint_(clone, rotationExtent, index);
textPoint.rotate(rotation, rotationCenter);
}
feature = this.featurePool_[poolIndex++];
feature.setGeometry(textPoint);
feature.set('graticule_label', this.parallelsLabels_[index].text);
vectorContext.drawFeature(feature, this.latLabelStyle_(feature));
}
} }
feature = this.featurePool_[poolIndex++];
feature.setGeometry(textPoint);
feature.set('graticule_label', this.parallelsLabels_[index].text);
vectorContext.drawFeature(feature, this.latLabelStyle_(feature));
} }
} }
} }
@@ -698,6 +729,18 @@ class Graticule extends VectorLayer {
return; return;
} }
let wrapX = false;
const projectionExtent = this.projection_.getExtent();
const worldWidth = getWidth(projectionExtent);
if (this.getSource().getWrapX() && this.projection_.canWrapX() && !containsExtent(projectionExtent, extent)) {
if (getWidth(extent) >= worldWidth) {
extent[0] = projectionExtent[0];
extent[2] = projectionExtent[2];
} else {
wrapX = true;
}
}
// Constrain the center to fit into the extent available to the graticule // Constrain the center to fit into the extent available to the graticule
const validCenterP = [ const validCenterP = [
@@ -721,44 +764,56 @@ class Graticule extends VectorLayer {
// Limit the extent to fit into the extent available to the graticule // Limit the extent to fit into the extent available to the graticule
const validExtentP = [ let validExtentP = extent;
clamp(extent[0], this.minX_, this.maxX_), if (!wrapX) {
clamp(extent[1], this.minY_, this.maxY_), validExtentP = [
clamp(extent[2], this.minX_, this.maxX_), clamp(extent[0], this.minX_, this.maxX_),
clamp(extent[3], this.minY_, this.maxY_) clamp(extent[1], this.minY_, this.maxY_),
]; clamp(extent[2], this.minX_, this.maxX_),
clamp(extent[3], this.minY_, this.maxY_)
];
}
// Transform the extent to get the lon lat ranges for the edges of the extent // Transform the extent to get the lon lat ranges for the edges of the extent
const validExtent = applyTransform(validExtentP, this.toLonLatTransform_, undefined, 8); const validExtent = applyTransform(validExtentP, this.toLonLatTransform_, undefined, 8);
// Check if extremities of the world extent lie inside the extent let maxLat = validExtent[3];
// (for example the pole in a polar projection) let maxLon = validExtent[2];
// and extend the extent as appropriate let minLat = validExtent[1];
let minLon = validExtent[0];
if (containsCoordinate(validExtentP, this.bottomLeft_)) { if (!wrapX) {
validExtent[0] = this.minLon_;
validExtent[1] = this.minLat_;
}
if (containsCoordinate(validExtentP, this.bottomRight_)) {
validExtent[2] = this.maxLon_;
validExtent[1] = this.minLat_;
}
if (containsCoordinate(validExtentP, this.topLeft_)) {
validExtent[0] = this.minLon_;
validExtent[3] = this.maxLat_;
}
if (containsCoordinate(validExtentP, this.topRight_)) {
validExtent[2] = this.maxLon_;
validExtent[3] = this.maxLat_;
}
// The transformed center may also extend the lon lat ranges used for rendering // Check if extremities of the world extent lie inside the extent
// (for example the pole in a polar projection)
// and extend the extent as appropriate
const maxLat = clamp(validExtent[3], centerLat, this.maxLat_); if (containsCoordinate(validExtentP, this.bottomLeft_)) {
const maxLon = clamp(validExtent[2], centerLon, this.maxLon_); minLon = this.minLon_;
const minLat = clamp(validExtent[1], this.minLat_, centerLat); minLat = this.minLat_;
const minLon = clamp(validExtent[0], this.minLon_, centerLon); }
if (containsCoordinate(validExtentP, this.bottomRight_)) {
maxLon = this.maxLon_;
minLat = this.minLat_;
}
if (containsCoordinate(validExtentP, this.topLeft_)) {
minLon = this.minLon_;
maxLat = this.maxLat_;
}
if (containsCoordinate(validExtentP, this.topRight_)) {
maxLon = this.maxLon_;
maxLat = this.maxLat_;
}
// The transformed center may also extend the lon lat ranges used for rendering
maxLat = clamp(maxLat, centerLat, this.maxLat_);
maxLon = clamp(maxLon, centerLon, this.maxLon_);
minLat = clamp(minLat, this.minLat_, centerLat);
minLon = clamp(minLon, this.minLon_, centerLon);
}
// Create meridians // Create meridians
@@ -768,17 +823,29 @@ class Graticule extends VectorLayer {
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0); idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0);
cnt = 0; cnt = 0;
while (lon != this.minLon_ && cnt++ < maxLines) { if (wrapX) {
lon = Math.max(lon - interval, this.minLon_); while ((lon -= interval) >= minLon && cnt++ < maxLines) {
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx); idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
}
} else {
while (lon != this.minLon_ && cnt++ < maxLines) {
lon = Math.max(lon - interval, this.minLon_);
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
}
} }
lon = clamp(centerLon, this.minLon_, this.maxLon_); lon = clamp(centerLon, this.minLon_, this.maxLon_);
cnt = 0; cnt = 0;
while (lon != this.maxLon_ && cnt++ < maxLines) { if (wrapX) {
lon = Math.min(lon + interval, this.maxLon_); while ((lon += interval) <= maxLon && cnt++ < maxLines) {
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx); idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
}
} else {
while (lon != this.maxLon_ && cnt++ < maxLines) {
lon = Math.min(lon + interval, this.maxLon_);
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
}
} }
this.meridians_.length = idx; this.meridians_.length = idx;

View File

@@ -6,6 +6,8 @@ import {createCanvasContext2D} from '../dom.js';
import {clear} from '../obj.js'; import {clear} from '../obj.js';
import BaseObject from '../Object.js'; import BaseObject from '../Object.js';
import EventTarget from '../events/Target.js'; import EventTarget from '../events/Target.js';
import {WORKER_OFFSCREEN_CANVAS} from '../has.js';
import {toString} from '../transform.js';
/** /**
@@ -297,34 +299,40 @@ export const measureTextHeight = (function() {
*/ */
let div; let div;
const heights = textHeights; const heights = textHeights;
return function(font) { return function(fontSpec) {
let height = heights[font]; let height = heights[fontSpec];
if (height == undefined) { if (height == undefined) {
if (!div) { if (WORKER_OFFSCREEN_CANVAS) {
div = document.createElement('div'); const font = getFontParameters(fontSpec);
div.innerHTML = 'M'; const metrics = measureText(fontSpec, 'Žg');
div.style.margin = '0 !important'; const lineHeight = isNaN(Number(font.lineHeight)) ? 1.2 : Number(font.lineHeight);
div.style.padding = '0 !important'; textHeights[fontSpec] = lineHeight * (metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent);
div.style.position = 'absolute !important'; } else {
div.style.left = '-99999px !important'; if (!div) {
div = document.createElement('div');
div.innerHTML = 'M';
div.style.margin = '0 !important';
div.style.padding = '0 !important';
div.style.position = 'absolute !important';
div.style.left = '-99999px !important';
}
div.style.font = fontSpec;
document.body.appendChild(div);
height = div.offsetHeight;
heights[fontSpec] = height;
document.body.removeChild(div);
} }
div.style.font = font;
document.body.appendChild(div);
height = div.offsetHeight;
heights[font] = height;
document.body.removeChild(div);
} }
return height; return height;
}; };
})(); })();
/** /**
* @param {string} font Font. * @param {string} font Font.
* @param {string} text Text. * @param {string} text Text.
* @return {number} Width. * @return {TextMetrics} Text metrics.
*/ */
export function measureTextWidth(font, text) { function measureText(font, text) {
if (!measureContext) { if (!measureContext) {
measureContext = createCanvasContext2D(1, 1); measureContext = createCanvasContext2D(1, 1);
} }
@@ -332,9 +340,17 @@ export function measureTextWidth(font, text) {
measureContext.font = font; measureContext.font = font;
measureFont = measureContext.font; measureFont = measureContext.font;
} }
return measureContext.measureText(text).width; return measureContext.measureText(text);
} }
/**
* @param {string} font Font.
* @param {string} text Text.
* @return {number} Width.
*/
export function measureTextWidth(font, text) {
return measureText(font, text).width;
}
/** /**
* Measure text width using a cache. * Measure text width using a cache.
@@ -432,9 +448,31 @@ function executeLabelInstructions(label, context) {
const contextInstructions = label.contextInstructions; const contextInstructions = label.contextInstructions;
for (let i = 0, ii = contextInstructions.length; i < ii; i += 2) { for (let i = 0, ii = contextInstructions.length; i < ii; i += 2) {
if (Array.isArray(contextInstructions[i + 1])) { if (Array.isArray(contextInstructions[i + 1])) {
CanvasRenderingContext2D.prototype[contextInstructions[i]].apply(context, contextInstructions[i + 1]); context[contextInstructions[i]].apply(context, contextInstructions[i + 1]);
} else { } else {
context[contextInstructions[i]] = contextInstructions[i + 1]; context[contextInstructions[i]] = contextInstructions[i + 1];
} }
} }
} }
/**
* @type {HTMLCanvasElement}
* @private
*/
let createTransformStringCanvas = null;
/**
* @param {import("../transform.js").Transform} transform Transform.
* @return {string} CSS transform.
*/
export function createTransformString(transform) {
if (WORKER_OFFSCREEN_CANVAS) {
return toString(transform);
} else {
if (!createTransformStringCanvas) {
createTransformStringCanvas = createCanvasContext2D(1, 1).canvas;
}
createTransformStringCanvas.style.transform = toString(transform);
return createTransformStringCanvas.style.transform;
}
}

View File

@@ -18,6 +18,7 @@ import {
} from '../../transform.js'; } from '../../transform.js';
import {defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js'; import {defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
import RBush from 'rbush/rbush.js'; import RBush from 'rbush/rbush.js';
import {WORKER_OFFSCREEN_CANVAS} from '../../has.js';
/** /**
@@ -204,7 +205,9 @@ class Executor {
contextInstructions.push('lineCap', strokeState.lineCap); contextInstructions.push('lineCap', strokeState.lineCap);
contextInstructions.push('lineJoin', strokeState.lineJoin); contextInstructions.push('lineJoin', strokeState.lineJoin);
contextInstructions.push('miterLimit', strokeState.miterLimit); contextInstructions.push('miterLimit', strokeState.miterLimit);
if (CanvasRenderingContext2D.prototype.setLineDash) { // eslint-disable-next-line
const Context = WORKER_OFFSCREEN_CANVAS ? OffscreenCanvasRenderingContext2D : CanvasRenderingContext2D;
if (Context.prototype.setLineDash) {
contextInstructions.push('setLineDash', [strokeState.lineDash]); contextInstructions.push('setLineDash', [strokeState.lineDash]);
contextInstructions.push('lineDashOffset', strokeState.lineDashOffset); contextInstructions.push('lineDashOffset', strokeState.lineDashOffset);
} }

View File

@@ -65,6 +65,10 @@ export function createHitDetectionImageData(size, transforms, features, styleFun
const image = originalStyle.getImage(); const image = originalStyle.getImage();
if (image) { if (image) {
const imgSize = image.getImageSize(); const imgSize = image.getImageSize();
if (!imgSize) {
continue;
}
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = imgSize[0]; canvas.width = imgSize[0];
canvas.height = imgSize[1]; canvas.height = imgSize[1];

View File

@@ -9,6 +9,7 @@ import {inView} from '../layer/Layer.js';
import {shared as iconImageCache} from '../style/IconImageCache.js'; import {shared as iconImageCache} from '../style/IconImageCache.js';
import {compose as composeTransform, makeInverse} from '../transform.js'; import {compose as composeTransform, makeInverse} from '../transform.js';
import {renderDeclutterItems} from '../render.js'; import {renderDeclutterItems} from '../render.js';
import {wrapX} from '../coordinate.js';
/** /**
* @abstract * @abstract
@@ -102,19 +103,12 @@ class MapRenderer extends Disposable {
const projection = viewState.projection; const projection = viewState.projection;
let translatedCoordinate = coordinate; const translatedCoordinate = wrapX(coordinate.slice(), projection);
const offsets = [[0, 0]]; const offsets = [[0, 0]];
if (projection.canWrapX()) { if (projection.canWrapX() && checkWrapped) {
const projectionExtent = projection.getExtent(); const projectionExtent = projection.getExtent();
const worldWidth = getWidth(projectionExtent); const worldWidth = getWidth(projectionExtent);
const x = coordinate[0]; offsets.push([-worldWidth, 0], [worldWidth, 0]);
if (x < projectionExtent[0] || x > projectionExtent[2]) {
const worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]];
}
if (checkWrapped) {
offsets.push([-worldWidth, 0], [worldWidth, 0]);
}
} }
const layerStates = frameState.layerStatesArray; const layerStates = frameState.layerStatesArray;

View File

@@ -8,6 +8,7 @@ import {fromUserExtent} from '../../proj.js';
import {getIntersection, isEmpty} from '../../extent.js'; import {getIntersection, isEmpty} from '../../extent.js';
import CanvasLayerRenderer from './Layer.js'; import CanvasLayerRenderer from './Layer.js';
import {compose as composeTransform, makeInverse} from '../../transform.js'; import {compose as composeTransform, makeInverse} from '../../transform.js';
import {createTransformString} from '../../render/canvas.js';
/** /**
* @classdesc * @classdesc
@@ -55,16 +56,20 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
} }
if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) { if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) {
let projection = viewState.projection; if (imageSource) {
if (!ENABLE_RASTER_REPROJECTION) { let projection = viewState.projection;
const sourceProjection = imageSource.getProjection(); if (!ENABLE_RASTER_REPROJECTION) {
if (sourceProjection) { const sourceProjection = imageSource.getProjection();
projection = sourceProjection; if (sourceProjection) {
projection = sourceProjection;
}
} }
} const image = imageSource.getImage(renderedExtent, viewResolution, pixelRatio, projection);
const image = imageSource.getImage(renderedExtent, viewResolution, pixelRatio, projection); if (image && this.loadImage(image)) {
if (image && this.loadImage(image)) { this.image_ = image;
this.image_ = image; }
} else {
this.image_ = null;
} }
} }
@@ -105,7 +110,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
); );
makeInverse(this.inversePixelTransform, this.pixelTransform); makeInverse(this.inversePixelTransform, this.pixelTransform);
const canvasTransform = this.createTransformString(this.pixelTransform); const canvasTransform = createTransformString(this.pixelTransform);
this.useContainer(target, canvasTransform, layerState.opacity); this.useContainer(target, canvasTransform, layerState.opacity);

View File

@@ -7,7 +7,7 @@ import RenderEvent from '../../render/Event.js';
import RenderEventType from '../../render/EventType.js'; import RenderEventType from '../../render/EventType.js';
import {rotateAtOffset} from '../../render/canvas.js'; import {rotateAtOffset} from '../../render/canvas.js';
import LayerRenderer from '../Layer.js'; import LayerRenderer from '../Layer.js';
import {create as createTransform, apply as applyTransform, compose as composeTransform, toString} from '../../transform.js'; import {create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js';
/** /**
* @abstract * @abstract
@@ -69,12 +69,6 @@ class CanvasLayerRenderer extends LayerRenderer {
*/ */
this.containerReused = false; this.containerReused = false;
/**
* @type {HTMLCanvasElement}
* @private
*/
this.createTransformStringCanvas_ = createCanvasContext2D(1, 1).canvas;
} }
/** /**
@@ -269,15 +263,7 @@ class CanvasLayerRenderer extends LayerRenderer {
return data; return data;
} }
/**
* @param {import("../../transform.js").Transform} transform Transform.
* @return {string} CSS transform.
*/
createTransformString(transform) {
this.createTransformStringCanvas_.style.transform = toString(transform);
return this.createTransformStringCanvas_.style.transform;
}
} }
export default CanvasLayerRenderer; export default CanvasLayerRenderer;

View File

@@ -9,6 +9,7 @@ import {createEmpty, equals, getIntersection, getTopLeft} from '../../extent.js'
import CanvasLayerRenderer from './Layer.js'; import CanvasLayerRenderer from './Layer.js';
import {apply as applyTransform, compose as composeTransform, makeInverse} from '../../transform.js'; import {apply as applyTransform, compose as composeTransform, makeInverse} from '../../transform.js';
import {numberSafeCompareFunction} from '../../array.js'; import {numberSafeCompareFunction} from '../../array.js';
import {createTransformString} from '../../render/canvas.js';
import {assign} from '../../obj.js'; import {assign} from '../../obj.js';
/** /**
@@ -244,7 +245,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
-width / 2, -height / 2 -width / 2, -height / 2
); );
const canvasTransform = this.createTransformString(this.pixelTransform); const canvasTransform = createTransformString(this.pixelTransform);
this.useContainer(target, canvasTransform, layerState.opacity); this.useContainer(target, canvasTransform, layerState.opacity);
const context = this.context; const context = this.context;

View File

@@ -3,7 +3,8 @@
*/ */
import {getUid} from '../../util.js'; import {getUid} from '../../util.js';
import ViewHint from '../../ViewHint.js'; import ViewHint from '../../ViewHint.js';
import {buffer, createEmpty, containsExtent, getWidth, intersects as intersectsExtent} from '../../extent.js'; import {buffer, createEmpty, containsExtent, getWidth, intersects as intersectsExtent, wrapX as wrapExtentX} from '../../extent.js';
import {wrapX as wrapCoordinateX} from '../../coordinate.js';
import {fromUserExtent, toUserExtent, getUserProjection, getTransformFromProjections} from '../../proj.js'; import {fromUserExtent, toUserExtent, getUserProjection, getTransformFromProjections} from '../../proj.js';
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js'; import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
@@ -361,9 +362,10 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const center = viewState.center.slice(); const center = viewState.center.slice();
const extent = buffer(frameStateExtent, const extent = buffer(frameStateExtent,
vectorLayerRenderBuffer * resolution); vectorLayerRenderBuffer * resolution);
const projectionExtent = viewState.projection.getExtent(); const loadExtents = [extent.slice()];
const projectionExtent = projection.getExtent();
if (vectorSource.getWrapX() && viewState.projection.canWrapX() && if (vectorSource.getWrapX() && projection.canWrapX() &&
!containsExtent(projectionExtent, frameState.extent)) { !containsExtent(projectionExtent, frameState.extent)) {
// For the replay group, we need an extent that intersects the real world // For the replay group, we need an extent that intersects the real world
// (-180° to +180°). To support geometries in a coordinate range from -540° // (-180° to +180°). To support geometries in a coordinate range from -540°
@@ -374,8 +376,14 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const gutter = Math.max(getWidth(extent) / 2, worldWidth); const gutter = Math.max(getWidth(extent) / 2, worldWidth);
extent[0] = projectionExtent[0] - gutter; extent[0] = projectionExtent[0] - gutter;
extent[2] = projectionExtent[2] + gutter; extent[2] = projectionExtent[2] + gutter;
const worldsAway = Math.floor((center[0] - projectionExtent[0]) / worldWidth); wrapCoordinateX(center, projection);
center[0] -= (worldsAway * worldWidth); const loadExtent = wrapExtentX(loadExtents[0], projection);
// If the extent crosses the date line, we load data for both edges of the worlds
if (loadExtent[0] < projectionExtent[0] && loadExtent[2] < projectionExtent[2]) {
loadExtents.push([loadExtent[0] + worldWidth, loadExtent[1], loadExtent[2] + worldWidth, loadExtent[3]]);
} else if (loadExtent[0] > projectionExtent[0] && loadExtent[2] > projectionExtent[2]) {
loadExtents.push([loadExtent[0] - worldWidth, loadExtent[1], loadExtent[2] - worldWidth, loadExtent[3]]);
}
} }
if (!this.dirty_ && if (!this.dirty_ &&
@@ -398,10 +406,14 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const userProjection = getUserProjection(); const userProjection = getUserProjection();
let userTransform; let userTransform;
if (userProjection) { if (userProjection) {
vectorSource.loadFeatures(toUserExtent(extent, projection), resolution, userProjection); for (let i = 0, ii = loadExtents.length; i < ii; ++i) {
vectorSource.loadFeatures(toUserExtent(loadExtents[i], projection), resolution, userProjection);
}
userTransform = getTransformFromProjections(userProjection, projection); userTransform = getTransformFromProjections(userProjection, projection);
} else { } else {
vectorSource.loadFeatures(extent, resolution, projection); for (let i = 0, ii = loadExtents.length; i < ii; ++i) {
vectorSource.loadFeatures(loadExtents[i], resolution, projection);
}
} }
const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio); const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio);

View File

@@ -6,7 +6,7 @@ import TileState from '../../TileState.js';
import ViewHint from '../../ViewHint.js'; import ViewHint from '../../ViewHint.js';
import {listen, unlistenByKey} from '../../events.js'; import {listen, unlistenByKey} from '../../events.js';
import EventType from '../../events/EventType.js'; import EventType from '../../events/EventType.js';
import {buffer, containsCoordinate, equals, getIntersection, intersects, containsExtent, getWidth, getTopLeft} from '../../extent.js'; import {buffer, containsCoordinate, equals, getIntersection, intersects, containsExtent, getTopLeft} from '../../extent.js';
import VectorTileRenderType from '../../layer/VectorTileRenderType.js'; import VectorTileRenderType from '../../layer/VectorTileRenderType.js';
import ReplayType from '../../render/canvas/BuilderType.js'; import ReplayType from '../../render/canvas/BuilderType.js';
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
@@ -25,6 +25,7 @@ import {
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js'; import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
import {clear} from '../../obj.js'; import {clear} from '../../obj.js';
import {createHitDetectionImageData, hitDetect} from '../../render/canvas/hitdetect.js'; import {createHitDetectionImageData, hitDetect} from '../../render/canvas/hitdetect.js';
import {wrapX} from '../../coordinate.js';
/** /**
@@ -353,9 +354,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
if (tile.getState() === TileState.LOADED && tile.hifi) { if (tile.getState() === TileState.LOADED && tile.hifi) {
const extent = tileGrid.getTileCoordExtent(tile.tileCoord); const extent = tileGrid.getTileCoordExtent(tile.tileCoord);
if (source.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) { if (source.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
const worldWidth = getWidth(projectionExtent); wrapX(coordinate, projection);
const worldsAway = Math.floor((coordinate[0] - projectionExtent[0]) / worldWidth);
coordinate[0] -= (worldsAway * worldWidth);
} }
break; break;
} }

View File

@@ -47,7 +47,7 @@ class ReprojImage extends ImageBase {
const triangulation = new Triangulation( const triangulation = new Triangulation(
sourceProj, targetProj, limitedTargetExtent, maxSourceExtent, sourceProj, targetProj, limitedTargetExtent, maxSourceExtent,
sourceResolution * errorThresholdInPixels); sourceResolution * errorThresholdInPixels, targetResolution);
const sourceExtent = triangulation.calculateSourceExtent(); const sourceExtent = triangulation.calculateSourceExtent();
const sourceImage = getImageFunction(sourceExtent, sourceResolution, pixelRatio); const sourceImage = getImageFunction(sourceExtent, sourceResolution, pixelRatio);

View File

@@ -168,7 +168,7 @@ class ReprojTile extends Tile {
*/ */
this.triangulation_ = new Triangulation( this.triangulation_ = new Triangulation(
sourceProj, targetProj, limitedTargetExtent, maxSourceExtent, sourceProj, targetProj, limitedTargetExtent, maxSourceExtent,
sourceResolution * errorThresholdInPixels); sourceResolution * errorThresholdInPixels, targetResolution);
if (this.triangulation_.getTriangles().length === 0) { if (this.triangulation_.getTriangles().length === 0) {
// no valid triangles -> EMPTY // no valid triangles -> EMPTY

View File

@@ -1,7 +1,7 @@
/** /**
* @module ol/reproj/Triangulation * @module ol/reproj/Triangulation
*/ */
import {boundingExtent, createEmpty, extendCoordinate, getBottomLeft, getBottomRight, import {boundingExtent, createEmpty, extendCoordinate, getArea, getBottomLeft, getBottomRight,
getTopLeft, getTopRight, getWidth, intersects} from '../extent.js'; getTopLeft, getTopRight, getWidth, intersects} from '../extent.js';
import {modulo} from '../math.js'; import {modulo} from '../math.js';
import {getTransform} from '../proj.js'; import {getTransform} from '../proj.js';
@@ -49,8 +49,9 @@ class Triangulation {
* @param {import("../extent.js").Extent} targetExtent Target extent to triangulate. * @param {import("../extent.js").Extent} targetExtent Target extent to triangulate.
* @param {import("../extent.js").Extent} maxSourceExtent Maximal source extent that can be used. * @param {import("../extent.js").Extent} maxSourceExtent Maximal source extent that can be used.
* @param {number} errorThreshold Acceptable error (in source units). * @param {number} errorThreshold Acceptable error (in source units).
* @param {?number} opt_destinationResolution The (optional) resolution of the destination.
*/ */
constructor(sourceProj, targetProj, targetExtent, maxSourceExtent, errorThreshold) { constructor(sourceProj, targetProj, targetExtent, maxSourceExtent, errorThreshold, opt_destinationResolution) {
/** /**
* @type {import("../proj/Projection.js").default} * @type {import("../proj/Projection.js").default}
@@ -138,11 +139,26 @@ class Triangulation {
const sourceBottomRight = this.transformInv_(destinationBottomRight); const sourceBottomRight = this.transformInv_(destinationBottomRight);
const sourceBottomLeft = this.transformInv_(destinationBottomLeft); const sourceBottomLeft = this.transformInv_(destinationBottomLeft);
/*
* The maxSubdivision controls how many splittings of the target area can
* be done. The idea here is to do a linear mapping of the target areas
* but the actual overal reprojection (can be) extremely non-linear. The
* default value of MAX_SUBDIVISION was chosen based on mapping a 256x256
* tile size. However this function is also called to remap canvas rendered
* layers which can be much larger. This calculation increases the maxSubdivision
* value by the right factor so that each 256x256 pixel area has
* MAX_SUBDIVISION divisions.
*/
const maxSubdivision = MAX_SUBDIVISION + (opt_destinationResolution ?
Math.max(0, Math.ceil(Math.log2(getArea(targetExtent) /
(opt_destinationResolution * opt_destinationResolution * 256 * 256))))
: 0);
this.addQuad_( this.addQuad_(
destinationTopLeft, destinationTopRight, destinationTopLeft, destinationTopRight,
destinationBottomRight, destinationBottomLeft, destinationBottomRight, destinationBottomLeft,
sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft, sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft,
MAX_SUBDIVISION); maxSubdivision);
if (this.wrapsXInSource_) { if (this.wrapsXInSource_) {
let leftBound = Infinity; let leftBound = Infinity;

View File

@@ -15,7 +15,7 @@ import Source from './Source.js';
/** /**
* @enum {string} * @enum {string}
*/ */
const ImageSourceEventType = { export const ImageSourceEventType = {
/** /**
* Triggered when an image starts loading. * Triggered when an image starts loading.

View File

@@ -190,7 +190,7 @@ class VectorSource extends Source {
* @private * @private
* @type {boolean} * @type {boolean}
*/ */
this.overlaps_ = options.overlaps == undefined ? true : options.overlaps; this.overlaps_ = options.overlaps === undefined ? true : options.overlaps;
/** /**
* @private * @private

View File

@@ -4,9 +4,8 @@
import {expandUrl, createFromTileUrlFunctions, nullTileUrlFunction} from '../tileurlfunction.js'; import {expandUrl, createFromTileUrlFunctions, nullTileUrlFunction} from '../tileurlfunction.js';
import {find, findIndex, includes} from '../array.js'; import {find, findIndex, includes} from '../array.js';
import {containsExtent} from '../extent.js';
import {assign} from '../obj.js'; import {assign} from '../obj.js';
import {get as getProjection, equivalent, transformExtent} from '../proj.js'; import {get as getProjection, equivalent} from '../proj.js';
import TileImage from './TileImage.js'; import TileImage from './TileImage.js';
import WMTSRequestEncoding from './WMTSRequestEncoding.js'; import WMTSRequestEncoding from './WMTSRequestEncoding.js';
import {createFromCapabilitiesMatrixSet} from '../tilegrid/WMTS.js'; import {createFromCapabilitiesMatrixSet} from '../tilegrid/WMTS.js';
@@ -379,22 +378,25 @@ export function optionsFromCapabilities(wmtsCap, config) {
} }
} }
const wgs84BoundingBox = l['WGS84BoundingBox']; const wrapX = false;
let extent, wrapX;
if (wgs84BoundingBox !== undefined) { const matrix0 = matrixSetObj.TileMatrix[0];
const wgs84ProjectionExtent = getProjection('EPSG:4326').getExtent(); const resolution = matrix0.ScaleDenominator * 0.00028; // WMTS 1.0.0: standardized rendering pixel size
wrapX = (wgs84BoundingBox[0] == wgs84ProjectionExtent[0] && const origin = projection === getProjection('EPSG:4326')
wgs84BoundingBox[2] == wgs84ProjectionExtent[2]); ? [matrix0.TopLeftCorner[1], matrix0.TopLeftCorner[0]]
extent = transformExtent( : matrix0.TopLeftCorner;
wgs84BoundingBox, 'EPSG:4326', projection); const tileSpanX = matrix0.TileWidth * resolution;
const projectionExtent = projection.getExtent(); const tileSpanY = matrix0.TileHeight * resolution;
if (projectionExtent) {
// If possible, do a sanity check on the extent - it should never be const extent = [
// bigger than the validity extent of the projection of a matrix set. origin[0],
if (!containsExtent(projectionExtent, extent)) { origin[1] - tileSpanY * matrix0.MatrixHeight,
extent = undefined; origin[0] + tileSpanX * matrix0.MatrixWidth,
} origin[1]
} ];
if (projection.getExtent() === null) {
projection.setExtent(extent);
} }
const tileGrid = createFromCapabilitiesMatrixSet(matrixSetObj, extent, matrixLimits); const tileGrid = createFromCapabilitiesMatrixSet(matrixSetObj, extent, matrixLimits);

View File

@@ -7,6 +7,14 @@
* @enum {string} * @enum {string}
*/ */
export default { export default {
/**
* Anchor is a fraction
* @api
*/
FRACTION: 'fraction', FRACTION: 'fraction',
/**
* Anchor is in pixels
* @api
*/
PIXELS: 'pixels' PIXELS: 'pixels'
}; };

View File

@@ -85,15 +85,18 @@ class IconImage extends EventTarget {
/** /**
* @private * @private
* @param {CanvasRenderingContext2D=} context A context with the image already drawn into.
* @return {boolean} The image canvas is tainted. * @return {boolean} The image canvas is tainted.
*/ */
isTainted_() { isTainted_(context) {
if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) { if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) {
this.tainted_ = false; if (!context) {
const context = createCanvasContext2D(1, 1); context = createCanvasContext2D(1, 1);
try {
context.drawImage(this.image_, 0, 0); context.drawImage(this.image_, 0, 0);
}
try {
context.getImageData(0, 0, 1, 1); context.getImageData(0, 0, 1, 1);
this.tainted_ = false;
} catch (e) { } catch (e) {
this.tainted_ = true; this.tainted_ = true;
} }
@@ -203,7 +206,7 @@ class IconImage extends EventTarget {
* @private * @private
*/ */
replaceColor_() { replaceColor_() {
if (!this.color_ || this.isTainted_()) { if (!this.color_) {
return; return;
} }
@@ -213,6 +216,25 @@ class IconImage extends EventTarget {
const ctx = this.canvas_.getContext('2d'); const ctx = this.canvas_.getContext('2d');
ctx.drawImage(this.image_, 0, 0); ctx.drawImage(this.image_, 0, 0);
if (this.isTainted_(ctx)) {
// If reading from the canvas throws a SecurityError the same effect can be
// achieved with globalCompositeOperation.
// This could be used as the default, but it is not fully supported by all
// browsers. E. g. Internet Explorer 11 does not support the multiply
// operation and the resulting image shape will be completelly filled with
// the provided color.
// So this is only used as a fallback. It is still better than having no icon
// at all.
const c = this.color_;
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = 'rgb(' + c[0] + ',' + c[1] + ',' + c[2] + ')';
ctx.fillRect(0, 0, this.image_.width, this.image_.height);
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(this.image_, 0, 0);
return;
}
const imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height); const imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height);
const data = imgData.data; const data = imgData.data;
const r = this.color_[0] / 255.0; const r = this.color_[0] / 255.0;

View File

@@ -7,8 +7,24 @@
* @enum {string} * @enum {string}
*/ */
export default { export default {
/**
* Origin is at bottom left
* @api
*/
BOTTOM_LEFT: 'bottom-left', BOTTOM_LEFT: 'bottom-left',
/**
* Origin is at bottom right
* @api
*/
BOTTOM_RIGHT: 'bottom-right', BOTTOM_RIGHT: 'bottom-right',
/**
* Origin is at top left
* @api
*/
TOP_LEFT: 'top-left', TOP_LEFT: 'top-left',
/**
* Origin is at top right
* @api
*/
TOP_RIGHT: 'top-right' TOP_RIGHT: 'top-right'
}; };

View File

@@ -1,5 +1,6 @@
import {add as addCoordinate, scale as scaleCoordinate, rotate as rotateCoordinate, equals as coordinatesEqual, format as formatCoordinate, closestOnCircle, closestOnSegment, createStringXY, squaredDistanceToSegment, toStringXY, toStringHDMS} from '../../../src/ol/coordinate.js'; import {add as addCoordinate, scale as scaleCoordinate, rotate as rotateCoordinate, equals as coordinatesEqual, format as formatCoordinate, closestOnCircle, closestOnSegment, createStringXY, squaredDistanceToSegment, toStringXY, toStringHDMS, wrapX} from '../../../src/ol/coordinate.js';
import Circle from '../../../src/ol/geom/Circle.js'; import Circle from '../../../src/ol/geom/Circle.js';
import {get} from '../../../src/ol/proj.js';
describe('ol.coordinate', function() { describe('ol.coordinate', function() {
@@ -235,4 +236,29 @@ describe('ol.coordinate', function() {
}); });
}); });
describe('wrapX()', function() {
const projection = get('EPSG:4326');
it('leaves real world coordinate untouched', function() {
expect(wrapX([16, 48], projection)).to.eql([16, 48]);
});
it('moves left world coordinate to real world', function() {
expect(wrapX([-344, 48], projection)).to.eql([16, 48]);
});
it('moves right world coordinate to real world', function() {
expect(wrapX([376, 48], projection)).to.eql([16, 48]);
});
it('moves far off left coordinate to real world', function() {
expect(wrapX([-1064, 48], projection)).to.eql([16, 48]);
});
it('moves far off right coordinate to real world', function() {
expect(wrapX([1096, 48], projection)).to.eql([16, 48]);
});
});
}); });

View File

@@ -1,5 +1,5 @@
import * as _ol_extent_ from '../../../src/ol/extent.js'; import * as _ol_extent_ from '../../../src/ol/extent.js';
import {getTransform} from '../../../src/ol/proj.js'; import {getTransform, get} from '../../../src/ol/proj.js';
import {register} from '../../../src/ol/proj/proj4.js'; import {register} from '../../../src/ol/proj/proj4.js';
@@ -818,4 +818,52 @@ describe('ol.extent', function() {
}); });
describe('wrapX()', function() {
const projection = get('EPSG:4326');
it('leaves real world extent untouched', function() {
expect(_ol_extent_.wrapX([16, 48, 18, 49], projection)).to.eql([16, 48, 18, 49]);
});
it('moves left world extent to real world', function() {
expect(_ol_extent_.wrapX([-344, 48, -342, 49], projection)).to.eql([16, 48, 18, 49]);
});
it('moves right world extent to real world', function() {
expect(_ol_extent_.wrapX([376, 48, 378, 49], projection)).to.eql([16, 48, 18, 49]);
});
it('moves far off left extent to real world', function() {
expect(_ol_extent_.wrapX([-1064, 48, -1062, 49], projection)).to.eql([16, 48, 18, 49]);
});
it('moves far off right extent to real world', function() {
expect(_ol_extent_.wrapX([1096, 48, 1098, 49], projection)).to.eql([16, 48, 18, 49]);
});
it('leaves -180 crossing extent with real world center untouched', function() {
expect(_ol_extent_.wrapX([-184, 48, 16, 49], projection)).to.eql([-184, 48, 16, 49]);
});
it('moves +180 crossing extent with off-world center to the real world', function() {
expect(_ol_extent_.wrapX([300, 48, 376, 49], projection)).to.eql([-60, 48, 16, 49]);
});
it('produces the same real world extent for shifted extents with center at +/-180', function() {
expect(_ol_extent_.wrapX([360, -90, 720, 90], projection)).to.eql([-360, -90, 0, 90]);
expect(_ol_extent_.wrapX([0, -90, 360, 90], projection)).to.eql([-360, -90, 0, 90]);
expect(_ol_extent_.wrapX([-360, -90, 0, 90], projection)).to.eql([-360, -90, 0, 90]);
});
});
describe('approximatelyEquals', function() {
it('returns true when within tolerance', function() {
expect(_ol_extent_.approximatelyEquals([16, 48, 17, 49], [16.09, 48, 17, 49], 0.1)).to.be(true);
});
it('returns false when not within tolerance', function() {
expect(_ol_extent_.approximatelyEquals([16, 48, 17, 49], [16.11, 48, 17, 49], 0.1)).to.be(false);
});
});
}); });

View File

@@ -1207,9 +1207,16 @@ describe('ol.format.KML', function() {
it('can write GeometryCollection geometries', function() { it('can write GeometryCollection geometries', function() {
const collection = new GeometryCollection([ const collection = new GeometryCollection([
new Point([1, 2]), new GeometryCollection([
new LineString([[1, 2], [3, 4]]), new Point([1, 2]),
new Polygon([[[1, 2], [3, 4], [3, 2], [1, 2]]]) new LineString([[1, 2], [3, 4]]),
new Polygon([[[1, 2], [3, 4], [3, 2], [1, 2]]])
]),
new GeometryCollection([
new MultiPoint([[5, 6], [9, 10]]),
new MultiLineString([[[5, 6], [7, 8]], [[9, 10], [11, 12]]]),
new MultiPolygon([[[[5, 6], [7, 8], [7, 6], [5, 6]]], [[[9, 10], [11, 12], [11, 10], [9, 10]]]])
])
]); ]);
const features = [new Feature(collection)]; const features = [new Feature(collection)];
const node = format.writeFeaturesNode(features); const node = format.writeFeaturesNode(features);
@@ -1234,6 +1241,32 @@ describe('ol.format.KML', function() {
' </LinearRing>' + ' </LinearRing>' +
' </outerBoundaryIs>' + ' </outerBoundaryIs>' +
' </Polygon>' + ' </Polygon>' +
' <Point>' +
' <coordinates>5,6</coordinates>' +
' </Point>' +
' <Point>' +
' <coordinates>9,10</coordinates>' +
' </Point>' +
' <LineString>' +
' <coordinates>5,6 7,8</coordinates>' +
' </LineString>' +
' <LineString>' +
' <coordinates>9,10 11,12</coordinates>' +
' </LineString>' +
' <Polygon>' +
' <outerBoundaryIs>' +
' <LinearRing>' +
' <coordinates>5,6 7,8 7,6 5,6</coordinates>' +
' </LinearRing>' +
' </outerBoundaryIs>' +
' </Polygon>' +
' <Polygon>' +
' <outerBoundaryIs>' +
' <LinearRing>' +
' <coordinates>9,10 11,12 11,10 9,10</coordinates>' +
' </LinearRing>' +
' </outerBoundaryIs>' +
' </Polygon>' +
' </MultiGeometry>' + ' </MultiGeometry>' +
' </Placemark>' + ' </Placemark>' +
'</kml>'; '</kml>';
@@ -1621,6 +1654,9 @@ describe('ol.format.KML', function() {
' <color>ff332211</color>' + ' <color>ff332211</color>' +
' <width>2</width>' + ' <width>2</width>' +
' </LineStyle>' + ' </LineStyle>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' </PolyStyle>' +
' </Style>' + ' </Style>' +
' <ExtendedData>' + ' <ExtendedData>' +
' <Data name="foo"/>' + ' <Data name="foo"/>' +
@@ -1992,6 +2028,37 @@ describe('ol.format.KML', function() {
expect(style.getZIndex()).to.be(undefined); expect(style.getZIndex()).to.be(undefined);
}); });
it('can read a feature\'s IconStyle and set color of image', () => {
const text =
'<kml xmlns="http://earth.google.com/kml/2.2"' +
' xmlns:gx="http://www.google.com/kml/ext/2.2">' +
' <Placemark>' +
' <Style>' +
' <IconStyle>' +
' <color>FF0000FF</color>' +
' <Icon>' +
' <href>http://foo.png</href>' +
' </Icon>' +
' </IconStyle>' +
' </Style>' +
' </Placemark>' +
'</kml>';
const fs = format.readFeatures(text);
expect(fs).to.have.length(1);
const f = fs[0];
expect(f).to.be.an(Feature);
const styleFunction = f.getStyleFunction();
const styleArray = styleFunction(f, 0);
expect(styleArray).to.be.an(Array);
expect(styleArray).to.have.length(1);
const style = styleArray[0];
expect(style).to.be.an(Style);
expect(style.getFill()).to.be(getDefaultFillStyle());
expect(style.getStroke()).to.be(getDefaultStrokeStyle());
const imageStyle = style.getImage();
expect(imageStyle.getColor()).to.eql([0xFF, 0, 0, 0xFF / 255]);
});
it('can read a feature\'s LabelStyle', function() { it('can read a feature\'s LabelStyle', function() {
const text = const text =
'<kml xmlns="http://earth.google.com/kml/2.2">' + '<kml xmlns="http://earth.google.com/kml/2.2">' +
@@ -2164,6 +2231,43 @@ describe('ol.format.KML', function() {
expect(strokeStyle.getWidth()).to.be(9); expect(strokeStyle.getWidth()).to.be(9);
expect(style.getText()).to.be(getDefaultTextStyle()); expect(style.getText()).to.be(getDefaultTextStyle());
expect(style.getZIndex()).to.be(undefined); expect(style.getZIndex()).to.be(undefined);
const lineString = new LineString([[1, 2], [3, 4]]);
const polygon = new Polygon([[[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]]]);
const collection = new GeometryCollection([lineString, polygon]);
f.setGeometry(collection);
const node = format.writeFeaturesNode(fs);
const text1 =
'<kml xmlns="http://www.opengis.net/kml/2.2"' +
' xmlns:gx="http://www.google.com/kml/ext/2.2"' +
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
' xsi:schemaLocation="http://www.opengis.net/kml/2.2' +
' https://developers.google.com/kml/schema/kml22gx.xsd">' +
' <Placemark>' +
' <Style>' +
' <LineStyle>' +
' <color>12345678</color>' +
' <width>9</width>' +
' </LineStyle>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' </PolyStyle>' +
' </Style>' +
' <MultiGeometry>' +
' <LineString>' +
' <coordinates>1,2 3,4</coordinates>' +
' </LineString>' +
' <Polygon>' +
' <outerBoundaryIs>' +
' <LinearRing>' +
' <coordinates>0,0 0,2 2,2 2,0 0,0</coordinates>' +
' </LinearRing>' +
' </outerBoundaryIs>' +
' </Polygon>' +
' </MultiGeometry>' +
' </Placemark>' +
'</kml>';
expect(node).to.xmleql(parse(text1));
}); });
it('disables the stroke when outline is \'0\'', function() { it('disables the stroke when outline is \'0\'', function() {
@@ -2233,6 +2337,41 @@ describe('ol.format.KML', function() {
expect(style1.getFill()).to.be(fillStyle); expect(style1.getFill()).to.be(fillStyle);
expect(style1.getStroke()).to.be(null); expect(style1.getStroke()).to.be(null);
expect(style1.getZIndex()).to.be(undefined); expect(style1.getZIndex()).to.be(undefined);
f.setGeometry(collectionFeature.getGeometry());
const node = format.writeFeaturesNode(fs);
const text1 =
'<kml xmlns="http://www.opengis.net/kml/2.2"' +
' xmlns:gx="http://www.google.com/kml/ext/2.2"' +
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
' xsi:schemaLocation="http://www.opengis.net/kml/2.2' +
' https://developers.google.com/kml/schema/kml22gx.xsd">' +
' <Placemark>' +
' <Style>' +
' <LineStyle>' +
' <color>12345678</color>' +
' <width>9</width>' +
' </LineStyle>' +
' <PolyStyle>' +
' <color>12345678</color>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' +
' <MultiGeometry>' +
' <LineString>' +
' <coordinates>1,2 3,4</coordinates>' +
' </LineString>' +
' <Polygon>' +
' <outerBoundaryIs>' +
' <LinearRing>' +
' <coordinates>0,0 0,2 2,2 2,0 0,0</coordinates>' +
' </LinearRing>' +
' </outerBoundaryIs>' +
' </Polygon>' +
' </MultiGeometry>' +
' </Placemark>' +
'</kml>';
expect(node).to.xmleql(parse(text1));
}); });
it('disables both fill and stroke when fill and outline are \'0\'', it('disables both fill and stroke when fill and outline are \'0\'',
@@ -2302,6 +2441,41 @@ describe('ol.format.KML', function() {
expect(style1.getFill()).to.be(null); expect(style1.getFill()).to.be(null);
expect(style1.getStroke()).to.be(null); expect(style1.getStroke()).to.be(null);
expect(style1.getZIndex()).to.be(undefined); expect(style1.getZIndex()).to.be(undefined);
f.setGeometry(collectionFeature.getGeometry());
const node = format.writeFeaturesNode(fs);
const text1 =
'<kml xmlns="http://www.opengis.net/kml/2.2"' +
' xmlns:gx="http://www.google.com/kml/ext/2.2"' +
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
' xsi:schemaLocation="http://www.opengis.net/kml/2.2' +
' https://developers.google.com/kml/schema/kml22gx.xsd">' +
' <Placemark>' +
' <Style>' +
' <LineStyle>' +
' <color>12345678</color>' +
' <width>9</width>' +
' </LineStyle>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' +
' <MultiGeometry>' +
' <LineString>' +
' <coordinates>1,2 3,4</coordinates>' +
' </LineString>' +
' <Polygon>' +
' <outerBoundaryIs>' +
' <LinearRing>' +
' <coordinates>0,0 0,2 2,2 2,0 0,0</coordinates>' +
' </LinearRing>' +
' </outerBoundaryIs>' +
' </Polygon>' +
' </MultiGeometry>' +
' </Placemark>' +
'</kml>';
expect(node).to.xmleql(parse(text1));
}); });
it('can create text style for named point placemarks (including html character codes)', function() { it('can create text style for named point placemarks (including html character codes)', function() {
@@ -2363,7 +2537,8 @@ describe('ol.format.KML', function() {
rotation: 45, rotation: 45,
scale: 0.5, scale: 0.5,
size: [48, 48], size: [48, 48],
src: 'http://foo.png' src: 'http://foo.png',
color: 'rgba(255,0,0,1)'
}) })
}); });
const imageStyle = style.getImage(); const imageStyle = style.getImage();
@@ -2389,9 +2564,14 @@ describe('ol.format.KML', function() {
' <gx:w>48</gx:w>' + ' <gx:w>48</gx:w>' +
' <gx:h>48</gx:h>' + ' <gx:h>48</gx:h>' +
' </Icon>' + ' </Icon>' +
' <color>ff0000ff</color>' +
' <hotSpot x="12" y="12" xunits="pixels" ' + ' <hotSpot x="12" y="12" xunits="pixels" ' +
' yunits="pixels"/>' + ' yunits="pixels"/>' +
' </IconStyle>' + ' </IconStyle>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' + ' </Style>' +
' </Placemark>' + ' </Placemark>' +
'</kml>'; '</kml>';
@@ -2440,6 +2620,10 @@ describe('ol.format.KML', function() {
' https://developers.google.com/kml/schema/kml22gx.xsd">' + ' https://developers.google.com/kml/schema/kml22gx.xsd">' +
' <Placemark>' + ' <Placemark>' +
' <Style>' + ' <Style>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' + ' </Style>' +
' </Placemark>' + ' </Placemark>' +
'</kml>'; '</kml>';
@@ -2472,13 +2656,17 @@ describe('ol.format.KML', function() {
' <color>ffdf220c</color>' + ' <color>ffdf220c</color>' +
' <scale>0.5</scale>' + ' <scale>0.5</scale>' +
' </LabelStyle>' + ' </LabelStyle>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' + ' </Style>' +
' </Placemark>' + ' </Placemark>' +
'</kml>'; '</kml>';
expect(node).to.xmleql(parse(text)); expect(node).to.xmleql(parse(text));
}); });
it('can write an feature\'s stroke style', function() { it('can write an feature\'s stroke style without fill', function() {
const style = new Style({ const style = new Style({
stroke: new Stroke({ stroke: new Stroke({
color: '#112233', color: '#112233',
@@ -2500,13 +2688,16 @@ describe('ol.format.KML', function() {
' <color>ff332211</color>' + ' <color>ff332211</color>' +
' <width>2</width>' + ' <width>2</width>' +
' </LineStyle>' + ' </LineStyle>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' </PolyStyle>' +
' </Style>' + ' </Style>' +
' </Placemark>' + ' </Placemark>' +
'</kml>'; '</kml>';
expect(node).to.xmleql(parse(text)); expect(node).to.xmleql(parse(text));
}); });
it('can write an feature\'s fill style', function() { it('can write an feature\'s fill style without outline', function() {
const style = new Style({ const style = new Style({
fill: new Fill({ fill: new Fill({
color: 'rgba(12, 34, 223, 0.7)' color: 'rgba(12, 34, 223, 0.7)'
@@ -2525,6 +2716,41 @@ describe('ol.format.KML', function() {
' <Style>' + ' <Style>' +
' <PolyStyle>' + ' <PolyStyle>' +
' <color>b2df220c</color>' + ' <color>b2df220c</color>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' +
' </Placemark>' +
'</kml>';
expect(node).to.xmleql(parse(text));
});
it('can write an feature\'s fill style and outline', function() {
const style = new Style({
fill: new Fill({
color: 'rgba(12, 34, 223, 0.7)'
}),
stroke: new Stroke({
color: '#112233',
width: 2
})
});
const feature = new Feature();
feature.setStyle([style]);
const node = format.writeFeaturesNode([feature]);
const text =
'<kml xmlns="http://www.opengis.net/kml/2.2"' +
' xmlns:gx="http://www.google.com/kml/ext/2.2"' +
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
' xsi:schemaLocation="http://www.opengis.net/kml/2.2' +
' https://developers.google.com/kml/schema/kml22gx.xsd">' +
' <Placemark>' +
' <Style>' +
' <LineStyle>' +
' <color>ff332211</color>' +
' <width>2</width>' +
' </LineStyle>' +
' <PolyStyle>' +
' <color>b2df220c</color>' +
' </PolyStyle>' + ' </PolyStyle>' +
' </Style>' + ' </Style>' +
' </Placemark>' + ' </Placemark>' +
@@ -2554,6 +2780,7 @@ describe('ol.format.KML', function() {
' <Style>' + ' <Style>' +
' <PolyStyle>' + ' <PolyStyle>' +
' <color>b2df220c</color>' + ' <color>b2df220c</color>' +
' <outline>0</outline>' +
' </PolyStyle>' + ' </PolyStyle>' +
' </Style>' + ' </Style>' +
' </Placemark>' + ' </Placemark>' +
@@ -2561,6 +2788,7 @@ describe('ol.format.KML', function() {
' <Style>' + ' <Style>' +
' <PolyStyle>' + ' <PolyStyle>' +
' <color>b2df220c</color>' + ' <color>b2df220c</color>' +
' <outline>0</outline>' +
' </PolyStyle>' + ' </PolyStyle>' +
' </Style>' + ' </Style>' +
' </Placemark>' + ' </Placemark>' +

View File

@@ -92,6 +92,9 @@ access interface to some TileMatrixSets</ows:Abstract>
<TileMatrixSetLink> <TileMatrixSetLink>
<TileMatrixSet>google3857</TileMatrixSet> <TileMatrixSet>google3857</TileMatrixSet>
</TileMatrixSetLink> </TileMatrixSetLink>
<TileMatrixSetLink>
<TileMatrixSet>google3857subset</TileMatrixSet>
</TileMatrixSetLink>
<ResourceURL format="image/png" resourceType="tile" template="http://www.example.com/wmts/coastlines/{TileMatrix}/{TileRow}/{TileCol}.png"/> <ResourceURL format="image/png" resourceType="tile" template="http://www.example.com/wmts/coastlines/{TileMatrix}/{TileRow}/{TileCol}.png"/>
<ResourceURL format="application/gml+xml; version=3.1" resourceType="FeatureInfo" template="http://www.example.com/wmts/coastlines/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}/{J}/{I}.xml"/> <ResourceURL format="application/gml+xml; version=3.1" resourceType="FeatureInfo" template="http://www.example.com/wmts/coastlines/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}/{J}/{I}.xml"/>
<Dimension> <Dimension>
@@ -372,6 +375,29 @@ access interface to some TileMatrixSets</ows:Abstract>
<MatrixHeight>7000</MatrixHeight> <MatrixHeight>7000</MatrixHeight>
</TileMatrix> </TileMatrix>
</TileMatrixSet> </TileMatrixSet>
</Contents> <TileMatrixSet>
<!-- A custom tile matrix set based on google3857 with tiles at each level covering only part of projection space (extent) -->
<ows:Identifier>google3857subset</ows:Identifier>
<ows:SupportedCRS>urn:ogc:def:crs:EPSG:6.18:3:3857</ows:SupportedCRS>
<TileMatrix>
<ows:Identifier>18</ows:Identifier>
<ScaleDenominator>2132.72958385</ScaleDenominator>
<TopLeftCorner>-10000000 10000000</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>1</MatrixWidth>
<MatrixHeight>1</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>18</ows:Identifier>
<ScaleDenominator>1066.36479193</ScaleDenominator>
<TopLeftCorner>-10000000 10000000</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>2</MatrixWidth>
<MatrixHeight>2</MatrixHeight>
</TileMatrix>
</TileMatrixSet>
</Contents>
<ServiceMetadataURL xlink:href="http://www.maps.bob/wmts/1.0.0/WMTSCapabilities.xml"/> <ServiceMetadataURL xlink:href="http://www.maps.bob/wmts/1.0.0/WMTSCapabilities.xml"/>
</Capabilities> </Capabilities>

View File

@@ -54,11 +54,13 @@ describe('ol.format.WMTSCapabilities', function() {
expect(layer.Style[0].LegendURL[0].format).to.be.eql('image/png'); expect(layer.Style[0].LegendURL[0].format).to.be.eql('image/png');
expect(layer.TileMatrixSetLink).to.be.an('array'); expect(layer.TileMatrixSetLink).to.be.an('array');
expect(layer.TileMatrixSetLink).to.have.length(2); expect(layer.TileMatrixSetLink).to.have.length(3);
expect(layer.TileMatrixSetLink[0].TileMatrixSet).to.be expect(layer.TileMatrixSetLink[0].TileMatrixSet).to.be
.eql('BigWorldPixel'); .eql('BigWorldPixel');
expect(layer.TileMatrixSetLink[1].TileMatrixSet).to.be expect(layer.TileMatrixSetLink[1].TileMatrixSet).to.be
.eql('google3857'); .eql('google3857');
expect(layer.TileMatrixSetLink[2].TileMatrixSet).to.be
.eql('google3857subset');
const wgs84Bbox = layer.WGS84BoundingBox; const wgs84Bbox = layer.WGS84BoundingBox;
expect(wgs84Bbox).to.be.an('array'); expect(wgs84Bbox).to.be.an('array');

View File

@@ -1,6 +1,6 @@
import Graticule from '../../../src/ol/layer/Graticule.js'; import Graticule from '../../../src/ol/layer/Graticule.js';
import Map from '../../../src/ol/Map.js'; import Map from '../../../src/ol/Map.js';
import {get as getProjection} from '../../../src/ol/proj.js'; import {fromLonLat, get as getProjection} from '../../../src/ol/proj.js';
import Stroke from '../../../src/ol/style/Stroke.js'; import Stroke from '../../../src/ol/style/Stroke.js';
import Text from '../../../src/ol/style/Text.js'; import Text from '../../../src/ol/style/Text.js';
import Feature from '../../../src/ol/Feature.js'; import Feature from '../../../src/ol/Feature.js';
@@ -31,7 +31,48 @@ describe('ol.layer.Graticule', function() {
expect(graticule.parallelsLabels_).to.be(null); expect(graticule.parallelsLabels_).to.be(null);
}); });
it('creates a graticule with labels', function() { it('creates a graticule with normal world labels', function() {
const feature = new Feature();
graticule = new Graticule({
showLabels: true,
wrapX: false
});
new Map({
layers: [graticule]
});
const extent = [-25614353.926475704, -7827151.696402049,
25614353.926475704, 7827151.696402049];
const projection = getProjection('EPSG:3857');
const resolution = 39135.75848201024;
graticule.loaderFunction(extent, resolution, projection);
const event = {
context: document.createElement('canvas').getContext('2d'),
inversePixelTransform: [1, 0, 0, 1, 0, 0],
frameState: {
coordinateToPixelTransform: [1, 0, 0, 1, 0, 0],
extent: extent,
pixelRatio: 1,
viewState: {
projection: projection,
resolution: resolution,
rotation: 0
}
}
};
graticule.drawLabels_(event);
expect(graticule.meridiansLabels_.length).to.be(13);
expect(graticule.meridiansLabels_[0].text).to.be('0° 00 00″');
expect(graticule.meridiansLabels_[0].geom.getCoordinates()[0]).to.roughlyEqual(0, 1e-9);
expect(graticule.parallelsLabels_.length).to.be(3);
expect(graticule.parallelsLabels_[0].text).to.be('0° 00 00″');
expect(graticule.parallelsLabels_[0].geom.getCoordinates()[1]).to.roughlyEqual(0, 1e-9);
feature.set('graticule_label', graticule.meridiansLabels_[0].text);
expect(graticule.lonLabelStyle_(feature).getText().getText()).to.be('0° 00 00″');
feature.set('graticule_label', graticule.parallelsLabels_[0].text);
expect(graticule.latLabelStyle_(feature).getText().getText()).to.be('0° 00 00″');
});
it('creates a graticule with wrapped world labels', function() {
const feature = new Feature(); const feature = new Feature();
graticule = new Graticule({ graticule = new Graticule({
showLabels: true showLabels: true
@@ -61,7 +102,8 @@ describe('ol.layer.Graticule', function() {
graticule.drawLabels_(event); graticule.drawLabels_(event);
expect(graticule.meridiansLabels_.length).to.be(13); expect(graticule.meridiansLabels_.length).to.be(13);
expect(graticule.meridiansLabels_[0].text).to.be('0° 00 00″'); expect(graticule.meridiansLabels_[0].text).to.be('0° 00 00″');
expect(graticule.meridiansLabels_[0].geom.getCoordinates()[0]).to.roughlyEqual(0, 1e-9); const coordinates = fromLonLat([360, 0]);
expect(graticule.meridiansLabels_[0].geom.getCoordinates()[0]).to.roughlyEqual(coordinates[0], 1e-9);
expect(graticule.parallelsLabels_.length).to.be(3); expect(graticule.parallelsLabels_.length).to.be(3);
expect(graticule.parallelsLabels_[0].text).to.be('0° 00 00″'); expect(graticule.parallelsLabels_[0].text).to.be('0° 00 00″');
expect(graticule.parallelsLabels_[0].geom.getCoordinates()[1]).to.roughlyEqual(0, 1e-9); expect(graticule.parallelsLabels_[0].geom.getCoordinates()[1]).to.roughlyEqual(0, 1e-9);

View File

@@ -14,6 +14,7 @@ import VectorLayer from '../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../src/ol/source/Vector.js'; import VectorSource from '../../../../src/ol/source/Vector.js';
import Event from '../../../../src/ol/events/Event.js'; import Event from '../../../../src/ol/events/Event.js';
import {getValues} from '../../../../src/ol/obj.js'; import {getValues} from '../../../../src/ol/obj.js';
import {clearUserProjection, setUserProjection} from '../../../../src/ol/proj.js';
describe('ol.interaction.Modify', function() { describe('ol.interaction.Modify', function() {
@@ -66,6 +67,7 @@ describe('ol.interaction.Modify', function() {
afterEach(function() { afterEach(function() {
map.dispose(); map.dispose();
document.body.removeChild(target); document.body.removeChild(target);
clearUserProjection();
}); });
/** /**
@@ -402,7 +404,7 @@ describe('ol.interaction.Modify', function() {
expect(circleFeature.getGeometry().getRadius()).to.equal(20); expect(circleFeature.getGeometry().getRadius()).to.equal(20);
expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]); expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]);
// Increase radius // Increase radius along x axis
simulateEvent('pointermove', 25, -4, null, 0); simulateEvent('pointermove', 25, -4, null, 0);
simulateEvent('pointerdown', 25, -4, null, 0); simulateEvent('pointerdown', 25, -4, null, 0);
simulateEvent('pointermove', 30, -5, null, 0); simulateEvent('pointermove', 30, -5, null, 0);
@@ -411,6 +413,64 @@ describe('ol.interaction.Modify', function() {
expect(circleFeature.getGeometry().getRadius()).to.equal(25); expect(circleFeature.getGeometry().getRadius()).to.equal(25);
expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]); expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]);
// Increase radius along y axis
simulateEvent('pointermove', 4, -30, null, 0);
simulateEvent('pointerdown', 4, -30, null, 0);
simulateEvent('pointermove', 5, -35, null, 0);
simulateEvent('pointerdrag', 5, -35, null, 0);
simulateEvent('pointerup', 5, -35, null, 0);
expect(circleFeature.getGeometry().getRadius()).to.equal(30);
expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]);
});
it('changes the circle radius and center in a user projection', function() {
const userProjection = 'EPSG:3857';
setUserProjection(userProjection);
const viewProjection = map.getView().getProjection();
const circleFeature = new Feature(new Circle([10, 10], 20).transform(viewProjection, userProjection));
features.length = 0;
features.push(circleFeature);
const modify = new Modify({
features: new Collection(features)
});
map.addInteraction(modify);
// Change center
simulateEvent('pointermove', 10, -10, null, 0);
simulateEvent('pointerdown', 10, -10, null, 0);
simulateEvent('pointermove', 5, -5, null, 0);
simulateEvent('pointerdrag', 5, -5, null, 0);
simulateEvent('pointerup', 5, -5, null, 0);
const geometry1 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection);
expect(geometry1.getRadius()).to.roughlyEqual(20, 1e-9);
expect(geometry1.getCenter()).to.eql([5, 5]);
// Increase radius along x axis
simulateEvent('pointermove', 25, -4, null, 0);
simulateEvent('pointerdown', 25, -4, null, 0);
simulateEvent('pointermove', 30, -5, null, 0);
simulateEvent('pointerdrag', 30, -5, null, 0);
simulateEvent('pointerup', 30, -5, null, 0);
const geometry2 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection);
expect(geometry2.getRadius()).to.roughlyEqual(25, 1e-9);
expect(geometry2.getCenter()).to.eql([5, 5]);
// Increase radius along y axis
simulateEvent('pointermove', 4, -30, null, 0);
simulateEvent('pointerdown', 4, -30, null, 0);
simulateEvent('pointermove', 5, -35, null, 0);
simulateEvent('pointerdrag', 5, -35, null, 0);
simulateEvent('pointerup', 5, -35, null, 0);
const geometry3 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection);
expect(geometry3.getRadius()).to.roughlyEqual(30, 1e-9);
expect(geometry3.getCenter()).to.eql([5, 5]);
}); });
}); });
@@ -766,7 +826,7 @@ describe('ol.interaction.Modify', function() {
expect(circleFeature.getGeometry().getRadius()).to.equal(20); expect(circleFeature.getGeometry().getRadius()).to.equal(20);
expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]); expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]);
// Increase radius // Increase radius along x axis
simulateEvent('pointermove', 25, -4, null, 0); simulateEvent('pointermove', 25, -4, null, 0);
simulateEvent('pointerdown', 25, -4, null, 0); simulateEvent('pointerdown', 25, -4, null, 0);
simulateEvent('pointermove', 30, -5, null, 0); simulateEvent('pointermove', 30, -5, null, 0);
@@ -775,6 +835,70 @@ describe('ol.interaction.Modify', function() {
expect(circleFeature.getGeometry().getRadius()).to.equal(25); expect(circleFeature.getGeometry().getRadius()).to.equal(25);
expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]); expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]);
// Increase radius along y axis
simulateEvent('pointermove', 4, -30, null, 0);
simulateEvent('pointerdown', 4, -30, null, 0);
simulateEvent('pointermove', 5, -35, null, 0);
simulateEvent('pointerdrag', 5, -35, null, 0);
simulateEvent('pointerup', 5, -35, null, 0);
expect(circleFeature.getGeometry().getRadius()).to.equal(30);
expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]);
});
it('changes the circle radius and center in a user projection', function() {
const userProjection = 'EPSG:3857';
setUserProjection(userProjection);
const viewProjection = map.getView().getProjection();
const circleFeature = new Feature(new Circle([10, 10], 20).transform(viewProjection, userProjection));
features.length = 0;
features.push(circleFeature);
const modify = new Modify({
features: new Collection(features)
});
map.addInteraction(modify);
const snap = new Snap({
features: new Collection(features),
pixelTolerance: 1
});
map.addInteraction(snap);
// Change center
simulateEvent('pointermove', 10, -10, null, 0);
simulateEvent('pointerdown', 10, -10, null, 0);
simulateEvent('pointermove', 5, -5, null, 0);
simulateEvent('pointerdrag', 5, -5, null, 0);
simulateEvent('pointerup', 5, -5, null, 0);
const geometry1 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection);
expect(geometry1.getRadius()).to.roughlyEqual(20, 1e-9);
expect(geometry1.getCenter()).to.eql([5, 5]);
// Increase radius along x axis
simulateEvent('pointermove', 25, -4, null, 0);
simulateEvent('pointerdown', 25, -4, null, 0);
simulateEvent('pointermove', 30, -5, null, 0);
simulateEvent('pointerdrag', 30, -5, null, 0);
simulateEvent('pointerup', 30, -5, null, 0);
const geometry2 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection);
expect(geometry2.getRadius()).to.roughlyEqual(25, 1e-9);
expect(geometry2.getCenter()).to.eql([5, 5]);
// Increase radius along y axis
simulateEvent('pointermove', 4, -30, null, 0);
simulateEvent('pointerdown', 4, -30, null, 0);
simulateEvent('pointermove', 5, -35, null, 0);
simulateEvent('pointerdrag', 5, -35, null, 0);
simulateEvent('pointerup', 5, -35, null, 0);
const geometry3 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection);
expect(geometry3.getRadius()).to.roughlyEqual(30, 1e-9);
expect(geometry3.getCenter()).to.eql([5, 5]);
}); });
}); });

View File

@@ -9,6 +9,7 @@ import Interaction from '../../../../src/ol/interaction/Interaction.js';
import Select from '../../../../src/ol/interaction/Select.js'; import Select from '../../../../src/ol/interaction/Select.js';
import VectorLayer from '../../../../src/ol/layer/Vector.js'; import VectorLayer from '../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../src/ol/source/Vector.js'; import VectorSource from '../../../../src/ol/source/Vector.js';
import Style from '../../../../src/ol/style/Style.js';
describe('ol.interaction.Select', function() { describe('ol.interaction.Select', function() {
@@ -406,4 +407,53 @@ describe('ol.interaction.Select', function() {
}); });
}); });
describe('clear event listeners on interaction removal', function() {
let firstInteraction, secondInteraction, feature;
beforeEach(function() {
feature = source.getFeatures()[3]; // top feature is selected
const style = new Style({});
const features = new Collection();
firstInteraction = new Select({style, features});
secondInteraction = new Select({style, features});
});
afterEach(function() {
map.removeInteraction(secondInteraction);
map.removeInteraction(firstInteraction);
});
// The base case
describe('with a single interaction added', function() {
it('changes the selected feature once', function() {
map.addInteraction(firstInteraction);
const listenerSpy = sinon.spy();
feature.on('change', listenerSpy);
simulateEvent('singleclick', 10, -20, false);
expect(listenerSpy.callCount).to.be(1);
});
});
// The "difficult" case. To prevent regression
describe('with a replaced interaction', function() {
it('changes the selected feature once', function() {
map.addInteraction(firstInteraction);
map.removeInteraction(firstInteraction);
map.addInteraction(secondInteraction);
const listenerSpy = sinon.spy();
feature.on('change', listenerSpy);
simulateEvent('singleclick', 10, -20, false);
expect(listenerSpy.callCount).to.be(1);
});
});
});
}); });

View File

@@ -6,7 +6,7 @@ import Circle from '../../../../src/ol/geom/Circle.js';
import Point from '../../../../src/ol/geom/Point.js'; import Point from '../../../../src/ol/geom/Point.js';
import LineString from '../../../../src/ol/geom/LineString.js'; import LineString from '../../../../src/ol/geom/LineString.js';
import Snap from '../../../../src/ol/interaction/Snap.js'; import Snap from '../../../../src/ol/interaction/Snap.js';
import {useGeographic, clearUserProjection} from '../../../../src/ol/proj.js'; import {useGeographic, clearUserProjection, setUserProjection, transform} from '../../../../src/ol/proj.js';
import {overrideRAF} from '../../util.js'; import {overrideRAF} from '../../util.js';
@@ -55,6 +55,7 @@ describe('ol.interaction.Snap', function() {
afterEach(function() { afterEach(function() {
map.dispose(); map.dispose();
document.body.removeChild(target); document.body.removeChild(target);
clearUserProjection();
}); });
it('can handle XYZ coordinates', function() { it('can handle XYZ coordinates', function() {
@@ -129,6 +130,30 @@ describe('ol.interaction.Snap', function() {
expect(event.coordinate[1]).to.roughlyEqual(Math.sin(Math.PI / 4) * 10, 1e-10); expect(event.coordinate[1]).to.roughlyEqual(Math.sin(Math.PI / 4) * 10, 1e-10);
}); });
it('snaps to circle in a user projection', function() {
const userProjection = 'EPSG:3857';
setUserProjection(userProjection);
const viewProjection = map.getView().getProjection();
const circle = new Feature(new Circle([0, 0], 10).transform(viewProjection, userProjection));
const snapInteraction = new Snap({
features: new Collection([circle]),
pixelTolerance: 5
});
snapInteraction.setMap(map);
const event = {
pixel: [5 + width / 2, height / 2 - 5],
coordinate: transform([5, 5], viewProjection, userProjection),
map: map
};
snapInteraction.handleEvent(event);
const coordinate = transform([Math.sin(Math.PI / 4) * 10, Math.sin(Math.PI / 4) * 10], viewProjection, userProjection);
expect(event.coordinate[0]).to.roughlyEqual(coordinate[0], 1e-10);
expect(event.coordinate[1]).to.roughlyEqual(coordinate[1], 1e-10);
});
it('handle feature without geometry', function() { it('handle feature without geometry', function() {
const feature = new Feature(); const feature = new Feature();
const snapInteraction = new Snap({ const snapInteraction = new Snap({

View File

@@ -6,6 +6,7 @@ import Feature from '../../../../src/ol/Feature.js';
import Point from '../../../../src/ol/geom/Point.js'; import Point from '../../../../src/ol/geom/Point.js';
import Map from '../../../../src/ol/Map.js'; import Map from '../../../../src/ol/Map.js';
import View from '../../../../src/ol/View.js'; import View from '../../../../src/ol/View.js';
import ImageStyle from '../../../../src/ol/style/Image.js';
describe('ol.layer.Vector', function() { describe('ol.layer.Vector', function() {
@@ -132,19 +133,41 @@ describe('ol.layer.Vector', function() {
let map, layer; let map, layer;
beforeEach(function() { beforeEach(function() {
const source = new VectorSource({
features: [
new Feature({
geometry: new Point([-1000000, 0]),
name: 'feature1'
}),
new Feature({
geometry: new Point([1000000, 0]),
name: 'feature2'
})
]
});
const feature = new Feature({
geometry: new Point([-1000000, 0]),
name: 'feature with no size'
});
const testImage = new ImageStyle({
opacity: 1,
displacement: []
});
testImage.getImageState = () => {};
testImage.listenImageChange = () => {};
testImage.getImageSize = () => {};
feature.setStyle([new Style({
image: testImage
})]);
source.addFeature(feature);
layer = new VectorLayer({ layer = new VectorLayer({
source: new VectorSource({ source
features: [
new Feature({
geometry: new Point([-1000000, 0]),
name: 'feature1'
}),
new Feature({
geometry: new Point([1000000, 0]),
name: 'feture2'
})
]
})
}); });
const container = document.createElement('div'); const container = document.createElement('div');
container.style.width = '256px'; container.style.width = '256px';
@@ -171,6 +194,7 @@ describe('ol.layer.Vector', function() {
map.renderSync(); map.renderSync();
const pixel = map.getPixelFromCoordinate([-1000000, 0]); const pixel = map.getPixelFromCoordinate([-1000000, 0]);
layer.getFeatures(pixel).then(function(features) { layer.getFeatures(pixel).then(function(features) {
expect(features.length).to.equal(1);
expect(features[0].get('name')).to.be('feature1'); expect(features[0].get('name')).to.be('feature1');
done(); done();
}); });

View File

@@ -1,11 +1,12 @@
import Feature from '../../../../../src/ol/Feature.js'; import Feature from '../../../../../src/ol/Feature.js';
import Map from '../../../../../src/ol/Map.js'; import Map from '../../../../../src/ol/Map.js';
import View from '../../../../../src/ol/View.js'; import View from '../../../../../src/ol/View.js';
import {buffer as bufferExtent, getWidth} from '../../../../../src/ol/extent.js'; import {buffer as bufferExtent, getWidth, getCenter} from '../../../../../src/ol/extent.js';
import Circle from '../../../../../src/ol/geom/Circle.js'; import Circle from '../../../../../src/ol/geom/Circle.js';
import Point from '../../../../../src/ol/geom/Point.js'; import Point from '../../../../../src/ol/geom/Point.js';
import {fromExtent} from '../../../../../src/ol/geom/Polygon.js'; import {fromExtent} from '../../../../../src/ol/geom/Polygon.js';
import VectorLayer from '../../../../../src/ol/layer/Vector.js'; import VectorLayer from '../../../../../src/ol/layer/Vector.js';
import {bbox as bboxStrategy} from '../../../../../src/ol/loadingstrategy.js';
import {get as getProjection} from '../../../../../src/ol/proj.js'; import {get as getProjection} from '../../../../../src/ol/proj.js';
import {checkedFonts} from '../../../../../src/ol/render/canvas.js'; import {checkedFonts} from '../../../../../src/ol/render/canvas.js';
import CanvasVectorLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorLayer.js'; import CanvasVectorLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorLayer.js';
@@ -220,21 +221,29 @@ describe('ol.renderer.canvas.VectorLayer', function() {
}); });
describe('#prepareFrame and #compose', function() { describe('#prepareFrame and #compose', function() {
let frameState, projExtent, renderer, worldWidth, buffer; let frameState, projExtent, renderer, worldWidth, buffer, loadExtents;
function loader(extent) {
loadExtents.push(extent);
}
beforeEach(function() { beforeEach(function() {
const layer = new VectorLayer({ const layer = new VectorLayer({
source: new VectorSource({wrapX: true}) source: new VectorSource({
wrapX: true,
loader: loader,
strategy: bboxStrategy
})
}); });
renderer = new CanvasVectorLayerRenderer(layer); renderer = new CanvasVectorLayerRenderer(layer);
const projection = getProjection('EPSG:3857'); const projection = getProjection('EPSG:3857');
projExtent = projection.getExtent(); projExtent = projection.getExtent();
worldWidth = getWidth(projExtent); worldWidth = getWidth(projExtent);
buffer = layer.getRenderBuffer(); buffer = layer.getRenderBuffer();
loadExtents = [];
frameState = { frameState = {
viewHints: [], viewHints: [],
viewState: { viewState: {
center: [0, 0],
projection: projection, projection: projection,
resolution: 1, resolution: 1,
rotation: 0 rotation: 0
@@ -242,55 +251,81 @@ describe('ol.renderer.canvas.VectorLayer', function() {
}; };
}); });
function setExtent(extent) {
frameState.extent = extent;
frameState.viewState.center = getCenter(extent);
}
it('sets correct extent for small viewport near dateline', function() { it('sets correct extent for small viewport near dateline', function() {
frameState.extent = setExtent([projExtent[0] - 10000, -10000, projExtent[0] + 10000, 10000]);
[projExtent[0] - 10000, -10000, projExtent[0] + 10000, 10000];
renderer.prepareFrame(frameState); renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(bufferExtent([ expect(renderer.replayGroup_.maxExtent_).to.eql(bufferExtent([
projExtent[0] - worldWidth + buffer, projExtent[0] - worldWidth + buffer,
-10000, projExtent[2] + worldWidth - buffer, 10000 -10000, projExtent[2] + worldWidth - buffer, 10000
], buffer)); ], buffer));
expect(loadExtents.length).to.be(2);
expect(loadExtents[0]).to.eql(bufferExtent(frameState.extent, buffer));
const otherExtent = [projExtent[2] - 10000, -10000, projExtent[2] + 10000, 10000];
expect(loadExtents[1]).to.eql(bufferExtent(otherExtent, buffer));
}); });
it('sets correct extent for viewport less than 1 world wide', function() { it('sets correct extent for viewport less than 1 world wide', function() {
frameState.extent = setExtent([projExtent[0] - 10000, -10000, projExtent[2] - 10000, 10000]);
[projExtent[0] - 10000, -10000, projExtent[1] - 10000, 10000];
renderer.prepareFrame(frameState); renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(bufferExtent([ expect(renderer.replayGroup_.maxExtent_).to.eql(bufferExtent([
projExtent[0] - worldWidth + buffer, projExtent[0] - worldWidth + buffer,
-10000, projExtent[2] + worldWidth - buffer, 10000 -10000, projExtent[2] + worldWidth - buffer, 10000
], buffer)); ], buffer));
expect(loadExtents.length).to.be(2);
expect(loadExtents[0]).to.eql(bufferExtent(frameState.extent, buffer));
const otherExtent = [projExtent[0] - 10000 + worldWidth, -10000, projExtent[2] - 10000 + worldWidth, 10000];
expect(loadExtents[1]).to.eql(bufferExtent(otherExtent, buffer));
}); });
it('sets correct extent for viewport more than 1 world wide', function() { it('sets correct extent for viewport more than 1 world wide', function() {
frameState.extent = setExtent([2 * projExtent[0] + 10000, -10000, 2 * projExtent[2] - 10000, 10000]);
[2 * projExtent[0] - 10000, -10000, 2 * projExtent[1] + 10000, 10000];
renderer.prepareFrame(frameState); renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(bufferExtent([ expect(renderer.replayGroup_.maxExtent_).to.eql(bufferExtent([
projExtent[0] - worldWidth + buffer, projExtent[0] - worldWidth + buffer,
-10000, projExtent[2] + worldWidth - buffer, 10000 -10000, projExtent[2] + worldWidth - buffer, 10000
], buffer)); ], buffer));
expect(loadExtents.length).to.be(1);
expect(loadExtents[0]).to.eql(bufferExtent(frameState.extent, buffer));
}); });
it('sets correct extent for viewport more than 2 worlds wide', function() { it('sets correct extent for viewport more than 2 worlds wide, one world away', function() {
frameState.extent = [ setExtent([projExtent[0] - 2 * worldWidth - 10000,
projExtent[0] - 2 * worldWidth - 10000, -10000, projExtent[0] + 2 * worldWidth + 10000, 10000
-10000, projExtent[1] + 2 * worldWidth + 10000, 10000 ]);
];
renderer.prepareFrame(frameState); renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(bufferExtent([ expect(renderer.replayGroup_.maxExtent_).to.eql(bufferExtent([
projExtent[0] - 2 * worldWidth - 10000, projExtent[0] - 2 * worldWidth - 10000,
-10000, projExtent[2] + 2 * worldWidth + 10000, 10000 -10000, projExtent[2] + 2 * worldWidth + 10000, 10000
], buffer)); ], buffer));
expect(loadExtents.length).to.be(1);
const normalizedExtent = [projExtent[0] - 2 * worldWidth + worldWidth - 10000, -10000, projExtent[0] + 2 * worldWidth + worldWidth + 10000, 10000];
expect(loadExtents[0]).to.eql(bufferExtent(normalizedExtent, buffer));
});
it('sets correct extent for small viewport, one world away', function() {
setExtent([-worldWidth - 10000, -10000, -worldWidth + 10000, 10000]);
renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(bufferExtent([
projExtent[0] - worldWidth + buffer,
-10000, projExtent[2] + worldWidth - buffer, 10000
], buffer));
expect(loadExtents.length).to.be(1);
const normalizedExtent = [-10000, -10000, 10000, 10000];
expect(loadExtents[0]).to.eql(bufferExtent(normalizedExtent, buffer));
}); });
it('sets replayGroupChanged correctly', function() { it('sets replayGroupChanged correctly', function() {
frameState.extent = [-10000, -10000, 10000, 10000]; setExtent([-10000, -10000, 10000, 10000]);
renderer.prepareFrame(frameState); renderer.prepareFrame(frameState);
expect(renderer.replayGroupChanged).to.be(true); expect(renderer.replayGroupChanged).to.be(true);
renderer.prepareFrame(frameState); renderer.prepareFrame(frameState);

View File

@@ -1,4 +1,5 @@
import WMTSCapabilities from '../../../../src/ol/format/WMTSCapabilities.js'; import WMTSCapabilities from '../../../../src/ol/format/WMTSCapabilities.js';
import {getBottomLeft, getTopRight} from '../../../../src/ol/extent.js';
import {get as getProjection} from '../../../../src/ol/proj.js'; import {get as getProjection} from '../../../../src/ol/proj.js';
import Projection from '../../../../src/ol/proj/Projection.js'; import Projection from '../../../../src/ol/proj/Projection.js';
import WMTSTileGrid from '../../../../src/ol/tilegrid/WMTS.js'; import WMTSTileGrid from '../../../../src/ol/tilegrid/WMTS.js';
@@ -149,6 +150,32 @@ describe('ol.source.WMTS', function() {
expect(options.projection.getCode()).to.be.eql('urn:ogc:def:crs:OGC:1.3:CRS84'); expect(options.projection.getCode()).to.be.eql('urn:ogc:def:crs:OGC:1.3:CRS84');
}); });
it('uses extent of tile matrix instead of projection extent', function() {
const options = optionsFromCapabilities(capabilities,
{layer: 'BlueMarbleNextGeneration', matrixSet: 'google3857subset'});
// Since google3857subset defines subset of space defined by the google3857 matrix set:
// - top left corner: -10000000, 10000000
// - calculated grid extent: [-10000000, 9999694.25188686, -9999694.25188686, 10000000]
// then the tile grid extent is only a part of the full projection extent.
const gridExtent = options.tileGrid.getExtent();
const gridBottomLeft = getBottomLeft(gridExtent);
const gridTopRight = getTopRight(gridExtent);
expect(Math.round(gridBottomLeft[0])).to.be.eql(-10000000);
expect(Math.round(gridBottomLeft[1])).to.be.eql(9999847);
expect(Math.round(gridTopRight[0])).to.be.eql(-9999847);
expect(Math.round(gridTopRight[1])).to.be.eql(10000000);
const projExtent = options.projection.getExtent();
const projBottomLeft = getBottomLeft(projExtent);
const projTopRight = getTopRight(projExtent);
expect(Math.round(projBottomLeft[0])).to.be.eql(-20037508);
expect(Math.round(projBottomLeft[1])).to.be.eql(-20037508);
expect(Math.round(projTopRight[0])).to.be.eql(20037508);
expect(Math.round(projTopRight[1])).to.be.eql(20037508);
});
it('doesn\'t fail if the GetCap doesn\'t contains Constraint tags', function() { it('doesn\'t fail if the GetCap doesn\'t contains Constraint tags', function() {
const tmpXml = content.replace(/<ows:Constraint[\s\S]*?<\/ows:Constraint>/g, ''); const tmpXml = content.replace(/<ows:Constraint[\s\S]*?<\/ows:Constraint>/g, '');
const tmpCapabilities = parser.read(tmpXml); const tmpCapabilities = parser.read(tmpXml);

View File

@@ -3,9 +3,10 @@
/* Basic Options */ /* Basic Options */
"target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": ["es2017", "dom"], /* Specify library files to be included in the compilation. */ "lib": ["es2017", "dom", "webworker"], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */ "allowJs": true, /* Allow javascript files to be compiled. */
"checkJs": true, /* Report errors in .js files. */ "checkJs": true, /* Report errors in .js files. */
"skipLibCheck": true,
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */