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 [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
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
### 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

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;
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.
exports.astNodeVisitor = {
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$/, '');
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) {
const doclets = e.doclets;
const byLongname = doclets.index.longname;
for (let i = doclets.length - 1; i >= 0; --i) {
const doclet = doclets[i];
if (doclet.stability) {
@@ -180,12 +179,14 @@ exports.handlers = {
doclet._hideConstructor = true;
includeAugments(doclet);
sortOtherMembers(doclet);
} else if (!doclet._hideConstructor && !(doclet.kind == 'typedef' && doclet.longname in types)) {
} else if (!doclet._hideConstructor) {
// Remove all other undocumented symbols
doclet.undocumented = true;
}
if (doclet._documented) {
delete doclet.undocumented;
if (doclet.memberof && byLongname[doclet.memberof] &&
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) {
var parts = f.split(/#?event:/);
var type = parts.pop();
var eventClassName = parts[0];
var eventClass = self.find({longname: parts[0]})[0];
parts = type.split(' ');
type = parts.shift();
var description = parts.length ? parts.join(' ') : '';
@@ -74,13 +74,10 @@ var self = this;
}
?>
<li class="<?js= (eventDoclet || data).stability !== 'stable' ? 'unstable' : '' ?>">
<code><?js= eventClassName ? self.linkto(f, type) : type ?></code>
<?js if (eventClassName) {
var eventClass = self.find({longname: eventClassName})[0];
if (eventClass) { ?>
<code><?js= eventClass ? self.linkto(f, type) : type ?></code>
<?js if (eventClass) { ?>
(<?js= self.linkto(eventClass.longname) ?>)
<?js } ?>
<?js } ?>
<?js= self.partial('stability.tmpl', eventDoclet || (data.stability ? data : {})) ?>
<?js if (description) { ?> -
<?js= 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>
tags: "bing, bing-maps"
cloak:
- key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5
- key: ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp
value: Your Bing Maps Key from http://www.bingmapsportal.com/ here
---
<div id="map" class="map"></div>

View File

@@ -18,7 +18,7 @@ for (i = 0, ii = styles.length; i < ii; ++i) {
visible: false,
preload: Infinity,
source: new BingMaps({
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5',
key: 'ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp ',
imagerySet: styles[i]
// use maxZoom 19 to see stretched tiles instead of the BingMaps
// "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">
<g>
<rect width="20" height="20" style="fill:#fff" />
<rect width="20" height="20" style="fill:#fff; stroke-width:4px; stroke:#000" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 194 B

After

Width:  |  Height:  |  Size: 225 B

View File

@@ -1,7 +1,7 @@
---
layout: example.html
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: >
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"

View File

@@ -25,6 +25,7 @@ rome.setStyle(new Style({
image: new Icon({
color: '#8959A8',
crossOrigin: 'anonymous',
imgSize: [20, 20],
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
shortdesc: Example of using ol-mapbox-style with tiles from maptiler.com.
docs: >
@@ -10,27 +10,4 @@ cloak:
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
value: Get your own API key at https://www.maptiler.com/cloud/
---
<!doctype html>
<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 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.
tags: "fullscreen, geolocation, mobile"
cloak:
- key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5
- key: ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp
value: Your Bing Maps Key from http://www.bingmapsportal.com/ here
---
<!doctype html>

View File

@@ -14,7 +14,7 @@ const map = new Map({
layers: [
new TileLayer({
source: new BingMaps({
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5',
key: 'ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp ',
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>
tags: "preload, bing"
cloak:
- key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5
- key: ApTJzdkyN1DdFKkRAE6QIDtzihNaf6IWJsT-nQ_2eMoO4PN__0Tzhl2-WgJtXFSp
value: Your Bing Maps Key from http://www.bingmapsportal.com/ here
---
<div id="map1" class="map"></div>

View File

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

View File

@@ -57,6 +57,8 @@
event.preventDefault();
const html = document.getElementById('example-html-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 form = document.getElementById('codepen-form');
@@ -68,8 +70,7 @@
Promise.all(promises)
.then(results => {
const data = {
files: {
const files = {
'index.html': {
content: html
},
@@ -82,7 +83,14 @@
'sandbox.config.json': {
content: '{"template": "parcel"}'
}
};
if (worker) {
files['worker.js'] = {
content: worker
}
}
const data = {
files: files
};
for (let i = 0; i < localResources.length; i++) {

View File

@@ -160,6 +160,14 @@
<pre><legend>index.js</legend><code id="example-js-source" class="language-js">import 'ol/ol.css';
{{ js.source }}</code></pre>
</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="source-controls">
<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>
</div>
</div>
<script src="./resources/common.js"></script>
<script src="./resources/prism/prism.min.js"></script>
{{{ js.tag }}}

View File

@@ -4,9 +4,9 @@ title: OSM XML
shortdesc: Example of using the OSM XML source.
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.
tags: "vector, osmxml, loading, server, strategy, bbox"
tags: "vector, osmxml, loading, server, strategy, bbox, maptiler"
cloak:
- key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5
value: Your Bing Maps Key from http://www.bingmapsportal.com/ here
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
value: Get your own API key at https://www.maptiler.com/cloud/
---
<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 {bbox as bboxStrategy} from '../src/ol/loadingstrategy.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 {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js';
@@ -85,8 +85,8 @@ const vectorSource = new VectorSource({
vectorSource.addFeatures(features);
});
const query = '(node(' +
epsg4326Extent[1] + ',' + epsg4326Extent[0] + ',' +
epsg4326Extent[3] + ',' + epsg4326Extent[2] +
epsg4326Extent[1] + ',' + Math.max(epsg4326Extent[0], -180) + ',' +
epsg4326Extent[3] + ',' + Math.min(epsg4326Extent[2], 180) +
');rel(bn)->.foo;way(bn);node(w)->.foo;rel(bw););out meta;';
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({
source: new BingMaps({
imagerySet: 'Aerial',
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5'
source: new XYZ({
attributions: attributions,
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);
}
}
// 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 = {
tag: `<script src="${this.common}.js"></script><script src="${jsName}"></script>`,
source: jsSource
@@ -218,9 +222,33 @@ ExampleBuilder.prototype.render = async function(dir, chunk) {
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({
name: name,
dependencies: getDependencies(jsSource),
dependencies: getDependencies(jsSource + workerSource ? `\n${workerSource}` : ''),
devDependencies: {
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",
"version": "6.2.2",
"version": "6.3.1-dev",
"description": "OpenLayers mapping library",
"keywords": [
"map",
@@ -24,6 +24,7 @@
"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",
"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"
},
"main": "index.js",
@@ -35,6 +36,10 @@
"bugs": {
"url": "https://github.com/openlayers/openlayers/issues"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/openlayers"
},
"dependencies": {
"elm-pep": "^1.0.4",
"pbf": "3.2.1",
@@ -50,25 +55,26 @@
"@types/pbf": "^3.0.2",
"@types/topojson-specification": "^1.0.1",
"babel-loader": "^8.0.5",
"buble": "^0.19.7",
"buble": "^0.20.0",
"buble-loader": "^0.5.1",
"chaikin-smooth": "^1.0.4",
"clean-css-cli": "4.3.0",
"copy-webpack-plugin": "^5.0.4",
"coveralls": "3.0.9",
"coveralls": "3.0.11",
"eslint": "^6.0.0",
"eslint-config-openlayers": "^13.0.0",
"expect.js": "0.3.1",
"front-matter": "^3.0.2",
"fs-extra": "^8.0.0",
"fs-extra": "^9.0.0",
"glob": "^7.1.5",
"globby": "^11.0.0",
"handlebars": "4.7.3",
"handlebars": "4.7.4",
"istanbul": "0.4.5",
"istanbul-instrumenter-loader": "^3.0.1",
"jquery": "3.4.1",
"jsdoc": "3.6.3",
"jsdoc-plugin-typescript": "^2.0.5",
"json-stringify-safe": "^5.0.1",
"karma": "^4.4.1",
"karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "^2.1.1",
@@ -77,14 +83,14 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.0-rc.2",
"loglevelnext": "^3.0.1",
"marked": "0.8.0",
"mocha": "7.1.0",
"ol-mapbox-style": "^6.0.0",
"marked": "0.8.2",
"mocha": "7.1.1",
"ol-mapbox-style": "^6.1.1",
"pixelmatch": "^5.1.0",
"pngjs": "^3.4.0",
"proj4": "2.6.0",
"proj4": "2.6.1",
"puppeteer": "~2.1.0",
"rollup": "^1.25.1",
"rollup": "^2.1.0",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
@@ -96,10 +102,11 @@
"typescript": "3.5.3",
"url-polyfill": "^1.1.5",
"walk": "^2.3.9",
"webpack": "4.42.0",
"webpack": "4.42.1",
"webpack-cli": "^3.3.2",
"webpack-dev-middleware": "^3.6.2",
"webpack-dev-server": "^3.3.1",
"worker-loader": "^2.0.0",
"yargs": "^15.0.2"
},
"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
* probably set `insertFirst` to `true` so the overlay is displayed below the
* controls.
* @property {boolean} [autoPan=false] If set to `true` the map is panned when
* calling `setPosition`, so that the overlay is entirely visible in the current
* viewport.
* @property {PanOptions} [autoPanAnimation] The
* animation options used to pan the overlay into view. This animation is only
* used when `autoPan` is enabled. A `duration` and `easing` may be provided to
* customize the animation.
* @property {PanIntoViewOptions|boolean} [autoPan=false] Pan the map when calling
* `setPosition`, so that the overlay is entirely visible in the current viewport?
* If `true` (deprecated), then `autoPanAnimation` and `autoPanMargin` will be
* used to determine the panning parameters; if an object is supplied then other
* parameters are ignored.
* @property {PanOptions} [autoPanAnimation] The animation options used to pan
* 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
* 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
* name.
*/
@@ -60,6 +66,12 @@ import {containsExtent} from './extent.js';
* 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}
@@ -137,38 +149,26 @@ class Overlay extends BaseObject {
options.className : 'ol-overlay-container ' + CLASS_SELECTABLE;
this.element.style.position = 'absolute';
let autoPan = options.autoPan;
if (autoPan && ('object' !== typeof autoPan)) {
autoPan = {
animation: options.autoPanAnimation,
margin: options.autoPanMargin
};
}
/**
* @protected
* @type {boolean}
* @type {PanIntoViewOptions|false}
*/
this.autoPan = options.autoPan !== undefined ? options.autoPan : false;
this.autoPan = /** @type {PanIntoViewOptions} */(autoPan) || false;
/**
* @protected
* @type {PanOptions}
*/
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,
* @type {{transform_: string,
* visible: boolean}}
*/
this.rendered = {
bottom_: '',
left_: '',
right_: '',
top_: '',
transform_: '',
visible: true
};
@@ -300,6 +300,7 @@ class Overlay extends BaseObject {
} else {
container.appendChild(this.element);
}
this.performAutoPan();
}
}
@@ -322,9 +323,7 @@ class Overlay extends BaseObject {
*/
handlePositionChanged() {
this.updatePixelPosition();
if (this.get(Property.POSITION) && this.autoPan) {
this.panIntoView();
}
this.performAutoPan();
}
/**
@@ -378,14 +377,26 @@ class Overlay extends BaseObject {
}
/**
* Pan the map so that the overlay is entirely visible in the current viewport
* (if necessary).
* Pan the map so that the overlay is entirely visisble in the current viewport
* (if necessary) using the configured autoPan parameters
* @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();
if (!map || !map.getTargetElement()) {
if (!map || !map.getTargetElement() || !this.get(Property.POSITION)) {
return;
}
@@ -393,7 +404,7 @@ class Overlay extends BaseObject {
const element = this.getElement();
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)) {
// the overlay is not completely inside the viewport, so pan the map
const offsetLeft = overlayRect[0] - mapRect[0];
@@ -404,17 +415,17 @@ class Overlay extends BaseObject {
const delta = [0, 0];
if (offsetLeft < 0) {
// move map to the left
delta[0] = offsetLeft - margin;
delta[0] = offsetLeft - myMargin;
} else if (offsetRight < 0) {
// move map to the right
delta[0] = Math.abs(offsetRight) + margin;
delta[0] = Math.abs(offsetRight) + myMargin;
}
if (offsetTop < 0) {
// move map up
delta[1] = offsetTop - margin;
delta[1] = offsetTop - myMargin;
} else if (offsetBottom < 0) {
// move map down
delta[1] = Math.abs(offsetBottom) + margin;
delta[1] = Math.abs(offsetBottom) + myMargin;
}
if (delta[0] !== 0 || delta[1] !== 0) {
@@ -425,10 +436,11 @@ class Overlay extends BaseObject {
centerPx[1] + delta[1]
];
const panOptions = panIntoViewOptions.animation || {};
map.getView().animateInternal({
center: map.getCoordinateFromPixelInternal(newCenterPx),
duration: this.autoPanAnimation.duration,
easing: this.autoPanAnimation.easing
duration: panOptions.duration,
easing: panOptions.easing
});
}
}
@@ -506,63 +518,34 @@ class Overlay extends BaseObject {
this.setVisible(true);
let offsetX = offset[0];
let offsetY = offset[1];
const x = Math.round(pixel[0] + offset[0]) + 'px';
const y = Math.round(pixel[1] + offset[1]) + 'px';
let posX = '0%';
let posY = '0%';
if (positioning == OverlayPositioning.BOTTOM_RIGHT ||
positioning == OverlayPositioning.CENTER_RIGHT ||
positioning == OverlayPositioning.TOP_RIGHT) {
if (this.rendered.left_ !== '') {
this.rendered.left_ = '';
style.left = '';
}
const right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px';
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 ||
posX = '-100%';
} else 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;
}
posX = '-50%';
}
if (positioning == OverlayPositioning.BOTTOM_LEFT ||
positioning == OverlayPositioning.BOTTOM_CENTER ||
positioning == OverlayPositioning.BOTTOM_RIGHT) {
if (this.rendered.top_ !== '') {
this.rendered.top_ = '';
style.top = '';
}
const bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px';
if (this.rendered.bottom_ != bottom) {
this.rendered.bottom_ = bottom;
style.bottom = bottom;
}
} else {
if (this.rendered.bottom_ !== '') {
this.rendered.bottom_ = '';
style.bottom = '';
}
if (positioning == OverlayPositioning.CENTER_LEFT ||
posY = '-100%';
} else 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;
posY = '-50%';
}
const transform = `translate(${posX}, ${posY}) translate(${x}, ${y})`;
if (this.rendered.transform_ != transform) {
this.rendered.transform_ = transform;
style.transform = transform;
// @ts-ignore IE9
style.msTransform = transform;
}
}

View File

@@ -12,7 +12,7 @@ import MapProperty from './MapProperty.js';
import RenderEventType from './render/EventType.js';
import BaseObject, {getChangeEventType} from './Object.js';
import ObjectEventType from './ObjectEventType.js';
import TileQueue from './TileQueue.js';
import TileQueue, {getTilePriority} from './TileQueue.js';
import View from './View.js';
import ViewHint from './ViewHint.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 LayerGroup from './layer/Group.js';
import {hasArea} from './size.js';
import {DROP} from './structs/PriorityQueue.js';
import {create as createTransform, apply as applyTransform} from './transform.js';
import {toUserCoordinate, fromUserCoordinate} from './proj.js';
@@ -891,26 +890,7 @@ class PluggableMap extends BaseObject {
* @return {number} Tile priority.
*/
getTilePriority(tile, tileSourceKey, tileCenter, tileResolution) {
// Filter out tiles at higher zoom levels than the current zoom level, or that
// 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;
return getTilePriority(this.frameState_, tile, tileSourceKey, tileCenter, tileResolution);
}
/**

View File

@@ -3,7 +3,7 @@
*/
import TileState from './TileState.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;
/**
* @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 {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) {
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
* @property {Array<string>} families
* @property {string} style
* @property {string} variant
* @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';
/**
* 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
* for font families that have commas in them.
* @param {string} The CSS font property.
* @return {FontParameters} The font families (or null if the input spec is invalid).
* @param {string} fontSpec The CSS font property.
* @return {FontParameters} The font parameters (or null if the input spec is invalid).
*/
export const getFontParameters = (function() {
/**
* @type {CSSStyleDeclaration}
*/
let style;
/**
* @type {Object<string, FontParameters>}
*/
const cache = {};
return function(font) {
if (!style) {
style = document.createElement('div').style;
}
if (!(font in cache)) {
style.font = font;
const family = style.fontFamily;
const fontWeight = style.fontWeight;
const fontStyle = style.fontStyle;
style.font = '';
if (!family) {
export const getFontParameters = function(fontSpec) {
const match = fontSpec.match(fontRegEx);
if (!match) {
return null;
}
const families = family.split(/,\s?/);
cache[font] = {
families: families,
weight: fontWeight,
style: fontStyle
};
const style = /** @type {FontParameters} */ ({
lineHeight: 'normal',
size: '1.2em',
style: 'normal',
weight: 'normal',
variant: 'normal'
});
for (let i = 0, ii = fontRegExMatchIndex.length; i < ii; ++i) {
const value = match[i + 1];
if (value !== undefined) {
style[fontRegExMatchIndex[i]] = value;
}
return cache[font];
}
style.families = style.family.split(/,\s?/);
return style;
};
})();

View File

@@ -1,8 +1,11 @@
import {WORKER_OFFSCREEN_CANVAS} from './has.js';
/**
* @module ol/dom
*/
//FIXME Move this function to the canvas module
/**
* Create an html canvas element and returns its 2d context.
* @param {number=} opt_width Canvas width.
@@ -12,14 +15,18 @@
*/
export function createCanvasContext2D(opt_width, opt_height, opt_canvasPool) {
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) {
canvas.width = opt_width;
}
if (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];
}
/**
* 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.
@@ -813,3 +825,25 @@ export function applyTransform(extent, transformFn, opt_extent, opt_stops) {
}
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({
image: imageStyle,
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()
text: textStyle
});
return nameStyle;
}
@@ -953,7 +949,7 @@ function createFeatureStyleFunction(style, styleUrl, defaultStyle, sharedStyles,
if (geometry) {
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
multiGeometryPoints = geometry.getGeometriesArray().filter(function(geometry) {
multiGeometryPoints = geometry.getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType();
return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
});
@@ -1171,6 +1167,7 @@ function readStyleMapValue(node, objectStack) {
const ICON_STYLE_PARSERS = makeStructureNS(
NAMESPACE_URIS, {
'Icon': makeObjectPropertySetter(readIcon),
'color': makeObjectPropertySetter(readColor),
'heading': makeObjectPropertySetter(readDecimal),
'hotSpot': makeObjectPropertySetter(readVec2),
'scale': makeObjectPropertySetter(readScale)
@@ -1252,6 +1249,9 @@ function iconStyleParser(node, objectStack) {
let scale = /** @type {number|undefined} */
(object['scale']);
const color = /** @type {Array<number>|undefined} */
(object['color']);
if (drawIcon) {
if (src == DEFAULT_IMAGE_STYLE_SRC) {
size = DEFAULT_IMAGE_STYLE_SIZE;
@@ -1271,7 +1271,8 @@ function iconStyleParser(node, objectStack) {
rotation: rotation,
scale: scale,
size: size,
src: src
src: src,
color: color
});
styleObject['imageStyle'] = imageStyle;
} else {
@@ -1806,7 +1807,7 @@ function readStyle(node, objectStack) {
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
return new GeometryCollection(
geometry.getGeometriesArray().filter(function(geometry) {
geometry.getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType();
return type !== GeometryType.POLYGON && type !== GeometryType.MULTI_POLYGON;
})
@@ -1827,7 +1828,7 @@ function readStyle(node, objectStack) {
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
return new GeometryCollection(
geometry.getGeometriesArray().filter(function(geometry) {
geometry.getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType();
return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
})
@@ -2447,7 +2448,7 @@ function writeIcon(node, icon, objectStack) {
// @ts-ignore
const ICON_STYLE_SEQUENCE = makeStructureNS(
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(
NAMESPACE_URIS, {
'Icon': makeChildAppender(writeIcon),
'color': makeChildAppender(writeColorTextNode),
'heading': makeChildAppender(writeDecimalTextNode),
'hotSpot': makeChildAppender(writeVec2),
'scale': makeChildAppender(writeScaleTextNode)
@@ -2514,6 +2516,11 @@ function writeIconStyle(node, style, objectStack) {
properties['heading'] = rotation; // 0-360
}
const color = style.getColor();
if (color) {
properties['color'] = color;
}
const parentNode = objectStack[objectStack.length - 1].node;
const orderedKeys = ICON_STYLE_SEQUENCE[parentNode.namespaceURI];
const values = makeSequence(properties, orderedKeys);
@@ -2703,20 +2710,35 @@ function writeMultiGeometry(node, geometry, objectStack) {
const context = {node: node};
const type = geometry.getType();
/** @type {Array<import("../geom/Geometry.js").default>} */
let geometries;
let geometries = [];
/** @type {function(*, Array<*>, string=): (Node|undefined)} */
let factory;
if (type == GeometryType.GEOMETRY_COLLECTION) {
geometries = /** @type {GeometryCollection} */ (geometry).getGeometries();
if (type === GeometryType.GEOMETRY_COLLECTION) {
/** @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;
} else if (type == GeometryType.MULTI_POINT) {
} else if (type === GeometryType.MULTI_POINT) {
geometries = /** @type {MultiPoint} */ (geometry).getPoints();
factory = POINT_NODE_FACTORY;
} else if (type == GeometryType.MULTI_LINE_STRING) {
} else if (type === GeometryType.MULTI_LINE_STRING) {
geometries =
(/** @type {MultiLineString} */ (geometry)).getLineStrings();
factory = LINE_STRING_NODE_FACTORY;
} else if (type == GeometryType.MULTI_POLYGON) {
} else if (type === GeometryType.MULTI_POLYGON) {
geometries =
(/** @type {MultiPolygon} */ (geometry)).getPolygons();
factory = POLYGON_NODE_FACTORY;
@@ -2831,16 +2853,64 @@ function writePlacemark(node, feature, objectStack) {
// resolution-independent here
const styles = styleFunction(feature, 0);
if (styles) {
const style = Array.isArray(styles) ? styles[0] : styles;
if (this.writeStyles_) {
properties['Style'] = style;
const styleArray = Array.isArray(styles) ? styles : [styles];
let pointStyles = styleArray;
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;
}
const textStyle = style.getText();
return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
}
});
}
if (this.writeStyles_) {
let lineStyles = styleArray;
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();
}
}
}
}
const parentNode = objectStack[objectStack.length - 1].node;
const orderedKeys = PLACEMARK_SEQUENCE[parentNode.namespaceURI];
const values = makeSequence(properties, orderedKeys);
@@ -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
* @type {Object<string, Object<string, import("../xml.js").Serializer>>}
@@ -2972,27 +3053,31 @@ function writePolygon(node, polygon, objectStack) {
// @ts-ignore
const POLY_STYLE_SERIALIZERS = makeStructureNS(
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 {Fill} style Style.
* @param {Style} style Style.
* @param {Array<*>} objectStack Object stack.
*/
function writePolyStyle(node, style, objectStack) {
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,
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 {Style} style Style.
* @param {Object<string, Array<Style>>} styles Styles.
* @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 properties = {};
const fillStyle = style.getFill();
const strokeStyle = style.getStroke();
const imageStyle = style.getImage();
const textStyle = style.getText();
if (imageStyle && typeof /** @type {?} */ (imageStyle).getSrc === 'function') {
properties['IconStyle'] = imageStyle;
}
if (styles.pointStyles.length) {
const textStyle = styles.pointStyles[0].getText();
if (textStyle) {
properties['LabelStyle'] = textStyle;
}
const imageStyle = styles.pointStyles[0].getImage();
if (imageStyle && typeof /** @type {?} */ (imageStyle).getSrc === 'function') {
properties['IconStyle'] = imageStyle;
}
}
if (styles.lineStyles.length) {
const strokeStyle = styles.lineStyles[0].getStroke();
if (strokeStyle) {
properties['LineStyle'] = strokeStyle;
}
if (fillStyle) {
properties['PolyStyle'] = fillStyle;
}
if (styles.polyStyles.length) {
const strokeStyle = styles.polyStyles[0].getStroke();
if (strokeStyle && !properties['LineStyle']) {
properties['LineStyle'] = strokeStyle;
}
properties['PolyStyle'] = styles.polyStyles[0];
}
const parentNode = objectStack[objectStack.length - 1].node;
const orderedKeys = STYLE_SEQUENCE[parentNode.namespaceURI];

View File

@@ -126,6 +126,23 @@ class GeometryCollection extends Geometry {
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
*/

View File

@@ -37,7 +37,15 @@ export const MAC = ua.indexOf('macintosh') !== -1;
* @type {number}
* @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.

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 GeometryType from '../geom/GeometryType.js';
import Point from '../geom/Point.js';
import {fromCircle} from '../geom/Polygon.js';
import PointerInteraction from './Pointer.js';
import VectorLayer from '../layer/Vector.js';
import VectorSource from '../source/Vector.js';
import VectorEventType from '../source/VectorEventType.js';
import RBush from '../structs/RBush.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;
circumferenceSegmentData.featureSegments = featureSegments;
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;
} else { // We're dragging the circle's circumference:
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;
}
break;
@@ -898,7 +915,14 @@ class Modify extends PointerInteraction {
circumferenceSegmentData.segment[0] = coordinates;
circumferenceSegmentData.segment[1] = coordinates;
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 {
this.rBush_.update(boundingExtent(segmentData.segment), segmentData);
}
@@ -1249,11 +1273,15 @@ function projectedDistanceToSegmentDataSquared(pointCoordinates, segmentData, pr
const geometry = segmentData.geometry;
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) {
const userProjection = getUserProjection();
if (userProjection) {
circleGeometry = /** @type {import("../geom/Circle.js").default} */ (circleGeometry.clone().transform(userProjection, projection));
}
const distanceToCenterSquared =
squaredCoordinateDistance(circleGeometry.getCenter(), pointCoordinates);
squaredCoordinateDistance(circleGeometry.getCenter(), fromUserCoordinate(pointCoordinates, projection));
const distanceToCircumference =
Math.sqrt(distanceToCenterSquared) - circleGeometry.getRadius();
return distanceToCircumference * distanceToCircumference;
@@ -1280,7 +1308,12 @@ function closestOnSegmentData(pointCoordinates, segmentData, projection) {
const geometry = segmentData.geometry;
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);
tempSegment[0] = fromUserCoordinate(segmentData.segment[0], projection);

View File

@@ -167,6 +167,16 @@ class Select extends Interaction {
const options = opt_options ? opt_options : {};
/**
* @private
*/
this.boundAddFeature_ = this.addFeature_.bind(this);
/**
* @private
*/
this.boundRemoveFeature_ = this.removeFeature_.bind(this);
/**
* @private
* @type {import("../events/condition.js").Condition}
@@ -249,10 +259,6 @@ class Select extends Interaction {
* @type {Object<string, import("../layer/Layer.js").default>}
*/
this.featureLayerAssociation_ = {};
const features = this.getFeatures();
features.addEventListener(CollectionEventType.ADD, this.addFeature_.bind(this));
features.addEventListener(CollectionEventType.REMOVE, this.removeFeature_.bind(this));
}
/**
@@ -320,9 +326,17 @@ class Select extends Interaction {
this.features_.forEach(this.restorePreviousStyle_.bind(this));
}
super.setMap(map);
if (map && this.style_) {
if (map) {
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 VectorEventType from '../source/VectorEventType.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_) {
const isCircle = closestSegmentData.feature.getGeometry().getType() === GeometryType.CIRCLE;
if (isCircle) {
vertex = closestOnCircle(pixelCoordinate,
/** @type {import("../geom/Circle.js").default} */ (closestSegmentData.feature.getGeometry()));
let circleGeometry = 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 {
tempSegment[0] = fromUserCoordinate(closestSegment[0], projection);
tempSegment[1] = fromUserCoordinate(closestSegment[1], projection);
@@ -482,7 +487,16 @@ class Snap extends PointerInteraction {
* @private
*/
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];
for (let i = 0, ii = coordinates.length - 1; i < ii; ++i) {
const segment = coordinates.slice(i, i + 2);

View File

@@ -17,13 +17,16 @@ import {
import {
applyTransform,
containsCoordinate,
containsExtent,
equals,
approximatelyEquals,
getCenter,
getHeight,
getIntersection,
getWidth,
intersects,
isEmpty
isEmpty,
wrapX as wrapExtentX
} from '../extent.js';
import {clamp} from '../math.js';
import Style from '../style/Style.js';
@@ -478,11 +481,21 @@ class Graticule extends VectorLayer {
* @return {Array<import("../extent.js").Extent>} Extents.
*/
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
let realWorldExtent = extent.slice();
if (this.projection_ && this.getSource().getWrapX()) {
wrapExtentX(realWorldExtent, this.projection_);
}
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 [extent];
}
return [realWorldExtent];
}
/**
@@ -622,9 +635,9 @@ class Graticule extends VectorLayer {
drawLabels_(event) {
const rotation = event.frameState.viewState.rotation;
const extent = event.frameState.extent;
let rotationCenter, rotationExtent;
const rotationCenter = getCenter(extent);
let rotationExtent = extent;
if (rotation) {
rotationCenter = getCenter(extent);
const width = getWidth(extent);
const height = getHeight(extent);
const cr = Math.abs(Math.cos(rotation));
@@ -637,17 +650,31 @@ class Graticule extends VectorLayer {
];
}
let startWorld = 0;
let endWorld = 0;
let labelsAtStart = this.latLabelPosition_ < 0.5;
const projectionExtent = this.projection_.getExtent();
const worldWidth = getWidth(projectionExtent);
if (this.getSource().getWrapX() && this.projection_.canWrapX() && !containsExtent(projectionExtent, extent)) {
startWorld = Math.floor((extent[0] - projectionExtent[0]) / worldWidth);
endWorld = Math.ceil((extent[2] - projectionExtent[2]) / worldWidth);
const inverted = Math.abs(rotation) > Math.PI / 2;
labelsAtStart = labelsAtStart !== inverted;
}
const vectorContext = getVectorContext(event);
for (let world = startWorld; world <= endWorld; ++world) {
let poolIndex = this.meridians_.length + this.parallels_.length;
let feature, index, l, textPoint;
if (this.meridiansLabels_) {
for (index = 0, l = this.meridiansLabels_.length; index < l; ++index) {
const lineString = this.meridians_[index];
if (!rotation) {
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);
@@ -659,12 +686,14 @@ class Graticule extends VectorLayer {
}
}
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) {
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);
@@ -676,6 +705,8 @@ class Graticule extends VectorLayer {
}
}
}
}
}
/**
* @param {import("../extent.js").Extent} extent Extent.
@@ -698,6 +729,18 @@ class Graticule extends VectorLayer {
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
const validCenterP = [
@@ -721,44 +764,56 @@ class Graticule extends VectorLayer {
// Limit the extent to fit into the extent available to the graticule
const validExtentP = [
let validExtentP = extent;
if (!wrapX) {
validExtentP = [
clamp(extent[0], this.minX_, this.maxX_),
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
const validExtent = applyTransform(validExtentP, this.toLonLatTransform_, undefined, 8);
let maxLat = validExtent[3];
let maxLon = validExtent[2];
let minLat = validExtent[1];
let minLon = validExtent[0];
if (!wrapX) {
// 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
if (containsCoordinate(validExtentP, this.bottomLeft_)) {
validExtent[0] = this.minLon_;
validExtent[1] = this.minLat_;
minLon = this.minLon_;
minLat = this.minLat_;
}
if (containsCoordinate(validExtentP, this.bottomRight_)) {
validExtent[2] = this.maxLon_;
validExtent[1] = this.minLat_;
maxLon = this.maxLon_;
minLat = this.minLat_;
}
if (containsCoordinate(validExtentP, this.topLeft_)) {
validExtent[0] = this.minLon_;
validExtent[3] = this.maxLat_;
minLon = this.minLon_;
maxLat = this.maxLat_;
}
if (containsCoordinate(validExtentP, this.topRight_)) {
validExtent[2] = this.maxLon_;
validExtent[3] = this.maxLat_;
maxLon = this.maxLon_;
maxLat = this.maxLat_;
}
// The transformed center may also extend the lon lat ranges used for rendering
const maxLat = clamp(validExtent[3], centerLat, this.maxLat_);
const maxLon = clamp(validExtent[2], centerLon, this.maxLon_);
const minLat = clamp(validExtent[1], this.minLat_, centerLat);
const minLon = clamp(validExtent[0], this.minLon_, centerLon);
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
@@ -768,18 +823,30 @@ class Graticule extends VectorLayer {
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0);
cnt = 0;
if (wrapX) {
while ((lon -= interval) >= minLon && cnt++ < maxLines) {
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_);
cnt = 0;
if (wrapX) {
while ((lon += interval) <= maxLon && cnt++ < maxLines) {
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;
if (this.meridiansLabels_) {

View File

@@ -6,6 +6,8 @@ import {createCanvasContext2D} from '../dom.js';
import {clear} from '../obj.js';
import BaseObject from '../Object.js';
import EventTarget from '../events/Target.js';
import {WORKER_OFFSCREEN_CANVAS} from '../has.js';
import {toString} from '../transform.js';
/**
@@ -297,9 +299,15 @@ export const measureTextHeight = (function() {
*/
let div;
const heights = textHeights;
return function(font) {
let height = heights[font];
return function(fontSpec) {
let height = heights[fontSpec];
if (height == undefined) {
if (WORKER_OFFSCREEN_CANVAS) {
const font = getFontParameters(fontSpec);
const metrics = measureText(fontSpec, 'Žg');
const lineHeight = isNaN(Number(font.lineHeight)) ? 1.2 : Number(font.lineHeight);
textHeights[fontSpec] = lineHeight * (metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent);
} else {
if (!div) {
div = document.createElement('div');
div.innerHTML = 'M';
@@ -308,23 +316,23 @@ export const measureTextHeight = (function() {
div.style.position = 'absolute !important';
div.style.left = '-99999px !important';
}
div.style.font = font;
div.style.font = fontSpec;
document.body.appendChild(div);
height = div.offsetHeight;
heights[font] = height;
heights[fontSpec] = height;
document.body.removeChild(div);
}
}
return height;
};
})();
/**
* @param {string} font Font.
* @param {string} text Text.
* @return {number} Width.
* @return {TextMetrics} Text metrics.
*/
export function measureTextWidth(font, text) {
function measureText(font, text) {
if (!measureContext) {
measureContext = createCanvasContext2D(1, 1);
}
@@ -332,9 +340,17 @@ export function measureTextWidth(font, text) {
measureContext.font = 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.
@@ -432,9 +448,31 @@ function executeLabelInstructions(label, context) {
const contextInstructions = label.contextInstructions;
for (let i = 0, ii = contextInstructions.length; i < ii; i += 2) {
if (Array.isArray(contextInstructions[i + 1])) {
CanvasRenderingContext2D.prototype[contextInstructions[i]].apply(context, contextInstructions[i + 1]);
context[contextInstructions[i]].apply(context, contextInstructions[i + 1]);
} else {
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';
import {defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.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('lineJoin', strokeState.lineJoin);
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('lineDashOffset', strokeState.lineDashOffset);
}

View File

@@ -65,6 +65,10 @@ export function createHitDetectionImageData(size, transforms, features, styleFun
const image = originalStyle.getImage();
if (image) {
const imgSize = image.getImageSize();
if (!imgSize) {
continue;
}
const canvas = document.createElement('canvas');
canvas.width = imgSize[0];
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 {compose as composeTransform, makeInverse} from '../transform.js';
import {renderDeclutterItems} from '../render.js';
import {wrapX} from '../coordinate.js';
/**
* @abstract
@@ -102,20 +103,13 @@ class MapRenderer extends Disposable {
const projection = viewState.projection;
let translatedCoordinate = coordinate;
const translatedCoordinate = wrapX(coordinate.slice(), projection);
const offsets = [[0, 0]];
if (projection.canWrapX()) {
if (projection.canWrapX() && checkWrapped) {
const projectionExtent = projection.getExtent();
const worldWidth = getWidth(projectionExtent);
const x = coordinate[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 numLayers = layerStates.length;

View File

@@ -8,6 +8,7 @@ import {fromUserExtent} from '../../proj.js';
import {getIntersection, isEmpty} from '../../extent.js';
import CanvasLayerRenderer from './Layer.js';
import {compose as composeTransform, makeInverse} from '../../transform.js';
import {createTransformString} from '../../render/canvas.js';
/**
* @classdesc
@@ -55,6 +56,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
}
if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) {
if (imageSource) {
let projection = viewState.projection;
if (!ENABLE_RASTER_REPROJECTION) {
const sourceProjection = imageSource.getProjection();
@@ -66,6 +68,9 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
if (image && this.loadImage(image)) {
this.image_ = image;
}
} else {
this.image_ = null;
}
}
return !!this.image_;
@@ -105,7 +110,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
);
makeInverse(this.inversePixelTransform, this.pixelTransform);
const canvasTransform = this.createTransformString(this.pixelTransform);
const canvasTransform = createTransformString(this.pixelTransform);
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 {rotateAtOffset} from '../../render/canvas.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
@@ -69,12 +69,6 @@ class CanvasLayerRenderer extends LayerRenderer {
*/
this.containerReused = false;
/**
* @type {HTMLCanvasElement}
* @private
*/
this.createTransformStringCanvas_ = createCanvasContext2D(1, 1).canvas;
}
/**
@@ -269,15 +263,7 @@ class CanvasLayerRenderer extends LayerRenderer {
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;

View File

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

View File

@@ -3,7 +3,8 @@
*/
import {getUid} from '../../util.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 CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
@@ -361,9 +362,10 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const center = viewState.center.slice();
const extent = buffer(frameStateExtent,
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)) {
// 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°
@@ -374,8 +376,14 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const gutter = Math.max(getWidth(extent) / 2, worldWidth);
extent[0] = projectionExtent[0] - gutter;
extent[2] = projectionExtent[2] + gutter;
const worldsAway = Math.floor((center[0] - projectionExtent[0]) / worldWidth);
center[0] -= (worldsAway * worldWidth);
wrapCoordinateX(center, projection);
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_ &&
@@ -398,10 +406,14 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const userProjection = getUserProjection();
let userTransform;
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);
} 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);

View File

@@ -6,7 +6,7 @@ import TileState from '../../TileState.js';
import ViewHint from '../../ViewHint.js';
import {listen, unlistenByKey} from '../../events.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 ReplayType from '../../render/canvas/BuilderType.js';
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
@@ -25,6 +25,7 @@ import {
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
import {clear} from '../../obj.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) {
const extent = tileGrid.getTileCoordExtent(tile.tileCoord);
if (source.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
const worldWidth = getWidth(projectionExtent);
const worldsAway = Math.floor((coordinate[0] - projectionExtent[0]) / worldWidth);
coordinate[0] -= (worldsAway * worldWidth);
wrapX(coordinate, projection);
}
break;
}

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
/**
* @module ol/reproj/Triangulation
*/
import {boundingExtent, createEmpty, extendCoordinate, getBottomLeft, getBottomRight,
import {boundingExtent, createEmpty, extendCoordinate, getArea, getBottomLeft, getBottomRight,
getTopLeft, getTopRight, getWidth, intersects} from '../extent.js';
import {modulo} from '../math.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} maxSourceExtent Maximal source extent that can be used.
* @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}
@@ -138,11 +139,26 @@ class Triangulation {
const sourceBottomRight = this.transformInv_(destinationBottomRight);
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_(
destinationTopLeft, destinationTopRight,
destinationBottomRight, destinationBottomLeft,
sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft,
MAX_SUBDIVISION);
maxSubdivision);
if (this.wrapsXInSource_) {
let leftBound = Infinity;

View File

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

View File

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

View File

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

View File

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

View File

@@ -85,15 +85,18 @@ class IconImage extends EventTarget {
/**
* @private
* @param {CanvasRenderingContext2D=} context A context with the image already drawn into.
* @return {boolean} The image canvas is tainted.
*/
isTainted_() {
isTainted_(context) {
if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) {
this.tainted_ = false;
const context = createCanvasContext2D(1, 1);
try {
if (!context) {
context = createCanvasContext2D(1, 1);
context.drawImage(this.image_, 0, 0);
}
try {
context.getImageData(0, 0, 1, 1);
this.tainted_ = false;
} catch (e) {
this.tainted_ = true;
}
@@ -203,7 +206,7 @@ class IconImage extends EventTarget {
* @private
*/
replaceColor_() {
if (!this.color_ || this.isTainted_()) {
if (!this.color_) {
return;
}
@@ -213,6 +216,25 @@ class IconImage extends EventTarget {
const ctx = this.canvas_.getContext('2d');
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 data = imgData.data;
const r = this.color_[0] / 255.0;

View File

@@ -7,8 +7,24 @@
* @enum {string}
*/
export default {
/**
* Origin is at bottom left
* @api
*/
BOTTOM_LEFT: 'bottom-left',
/**
* Origin is at bottom right
* @api
*/
BOTTOM_RIGHT: 'bottom-right',
/**
* Origin is at top left
* @api
*/
TOP_LEFT: 'top-left',
/**
* Origin is at top right
* @api
*/
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 {get} from '../../../src/ol/proj.js';
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 {getTransform} from '../../../src/ol/proj.js';
import {getTransform, get} from '../../../src/ol/proj.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() {
const collection = new GeometryCollection([
new GeometryCollection([
new Point([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 node = format.writeFeaturesNode(features);
@@ -1234,6 +1241,32 @@ describe('ol.format.KML', function() {
' </LinearRing>' +
' </outerBoundaryIs>' +
' </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>' +
' </Placemark>' +
'</kml>';
@@ -1621,6 +1654,9 @@ describe('ol.format.KML', function() {
' <color>ff332211</color>' +
' <width>2</width>' +
' </LineStyle>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' </PolyStyle>' +
' </Style>' +
' <ExtendedData>' +
' <Data name="foo"/>' +
@@ -1992,6 +2028,37 @@ describe('ol.format.KML', function() {
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() {
const text =
'<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(style.getText()).to.be(getDefaultTextStyle());
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() {
@@ -2233,6 +2337,41 @@ describe('ol.format.KML', function() {
expect(style1.getFill()).to.be(fillStyle);
expect(style1.getStroke()).to.be(null);
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\'',
@@ -2302,6 +2441,41 @@ describe('ol.format.KML', function() {
expect(style1.getFill()).to.be(null);
expect(style1.getStroke()).to.be(null);
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() {
@@ -2363,7 +2537,8 @@ describe('ol.format.KML', function() {
rotation: 45,
scale: 0.5,
size: [48, 48],
src: 'http://foo.png'
src: 'http://foo.png',
color: 'rgba(255,0,0,1)'
})
});
const imageStyle = style.getImage();
@@ -2389,9 +2564,14 @@ describe('ol.format.KML', function() {
' <gx:w>48</gx:w>' +
' <gx:h>48</gx:h>' +
' </Icon>' +
' <color>ff0000ff</color>' +
' <hotSpot x="12" y="12" xunits="pixels" ' +
' yunits="pixels"/>' +
' </IconStyle>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' +
' </Placemark>' +
'</kml>';
@@ -2440,6 +2620,10 @@ describe('ol.format.KML', function() {
' https://developers.google.com/kml/schema/kml22gx.xsd">' +
' <Placemark>' +
' <Style>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' +
' </Placemark>' +
'</kml>';
@@ -2472,13 +2656,17 @@ describe('ol.format.KML', function() {
' <color>ffdf220c</color>' +
' <scale>0.5</scale>' +
' </LabelStyle>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' +
' </Placemark>' +
'</kml>';
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({
stroke: new Stroke({
color: '#112233',
@@ -2500,13 +2688,16 @@ describe('ol.format.KML', function() {
' <color>ff332211</color>' +
' <width>2</width>' +
' </LineStyle>' +
' <PolyStyle>' +
' <fill>0</fill>' +
' </PolyStyle>' +
' </Style>' +
' </Placemark>' +
'</kml>';
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({
fill: new Fill({
color: 'rgba(12, 34, 223, 0.7)'
@@ -2525,6 +2716,41 @@ describe('ol.format.KML', function() {
' <Style>' +
' <PolyStyle>' +
' <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>' +
' </Style>' +
' </Placemark>' +
@@ -2554,6 +2780,7 @@ describe('ol.format.KML', function() {
' <Style>' +
' <PolyStyle>' +
' <color>b2df220c</color>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' +
' </Placemark>' +
@@ -2561,6 +2788,7 @@ describe('ol.format.KML', function() {
' <Style>' +
' <PolyStyle>' +
' <color>b2df220c</color>' +
' <outline>0</outline>' +
' </PolyStyle>' +
' </Style>' +
' </Placemark>' +

View File

@@ -92,6 +92,9 @@ access interface to some TileMatrixSets</ows:Abstract>
<TileMatrixSetLink>
<TileMatrixSet>google3857</TileMatrixSet>
</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="application/gml+xml; version=3.1" resourceType="FeatureInfo" template="http://www.example.com/wmts/coastlines/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}/{J}/{I}.xml"/>
<Dimension>
@@ -372,6 +375,29 @@ access interface to some TileMatrixSets</ows:Abstract>
<MatrixHeight>7000</MatrixHeight>
</TileMatrix>
</TileMatrixSet>
<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"/>
</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.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
.eql('BigWorldPixel');
expect(layer.TileMatrixSetLink[1].TileMatrixSet).to.be
.eql('google3857');
expect(layer.TileMatrixSetLink[2].TileMatrixSet).to.be
.eql('google3857subset');
const wgs84Bbox = layer.WGS84BoundingBox;
expect(wgs84Bbox).to.be.an('array');

View File

@@ -1,6 +1,6 @@
import Graticule from '../../../src/ol/layer/Graticule.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 Text from '../../../src/ol/style/Text.js';
import Feature from '../../../src/ol/Feature.js';
@@ -31,7 +31,48 @@ describe('ol.layer.Graticule', function() {
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();
graticule = new Graticule({
showLabels: true
@@ -61,7 +102,8 @@ describe('ol.layer.Graticule', function() {
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);
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_[0].text).to.be('0° 00 00″');
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 Event from '../../../../src/ol/events/Event.js';
import {getValues} from '../../../../src/ol/obj.js';
import {clearUserProjection, setUserProjection} from '../../../../src/ol/proj.js';
describe('ol.interaction.Modify', function() {
@@ -66,6 +67,7 @@ describe('ol.interaction.Modify', function() {
afterEach(function() {
map.dispose();
document.body.removeChild(target);
clearUserProjection();
});
/**
@@ -402,7 +404,7 @@ describe('ol.interaction.Modify', function() {
expect(circleFeature.getGeometry().getRadius()).to.equal(20);
expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]);
// Increase radius
// Increase radius along x axis
simulateEvent('pointermove', 25, -4, null, 0);
simulateEvent('pointerdown', 25, -4, 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().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().getCenter()).to.eql([5, 5]);
// Increase radius
// Increase radius along x axis
simulateEvent('pointermove', 25, -4, null, 0);
simulateEvent('pointerdown', 25, -4, 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().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 VectorLayer from '../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../src/ol/source/Vector.js';
import Style from '../../../../src/ol/style/Style.js';
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 LineString from '../../../../src/ol/geom/LineString.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';
@@ -55,6 +55,7 @@ describe('ol.interaction.Snap', function() {
afterEach(function() {
map.dispose();
document.body.removeChild(target);
clearUserProjection();
});
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);
});
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() {
const feature = new Feature();
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 Map from '../../../../src/ol/Map.js';
import View from '../../../../src/ol/View.js';
import ImageStyle from '../../../../src/ol/style/Image.js';
describe('ol.layer.Vector', function() {
@@ -132,8 +133,7 @@ describe('ol.layer.Vector', function() {
let map, layer;
beforeEach(function() {
layer = new VectorLayer({
source: new VectorSource({
const source = new VectorSource({
features: [
new Feature({
geometry: new Point([-1000000, 0]),
@@ -141,10 +141,33 @@ describe('ol.layer.Vector', function() {
}),
new Feature({
geometry: new Point([1000000, 0]),
name: 'feture2'
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({
source
});
const container = document.createElement('div');
container.style.width = '256px';
@@ -171,6 +194,7 @@ describe('ol.layer.Vector', function() {
map.renderSync();
const pixel = map.getPixelFromCoordinate([-1000000, 0]);
layer.getFeatures(pixel).then(function(features) {
expect(features.length).to.equal(1);
expect(features[0].get('name')).to.be('feature1');
done();
});

View File

@@ -1,11 +1,12 @@
import Feature from '../../../../../src/ol/Feature.js';
import Map from '../../../../../src/ol/Map.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 Point from '../../../../../src/ol/geom/Point.js';
import {fromExtent} from '../../../../../src/ol/geom/Polygon.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 {checkedFonts} from '../../../../../src/ol/render/canvas.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() {
let frameState, projExtent, renderer, worldWidth, buffer;
let frameState, projExtent, renderer, worldWidth, buffer, loadExtents;
function loader(extent) {
loadExtents.push(extent);
}
beforeEach(function() {
const layer = new VectorLayer({
source: new VectorSource({wrapX: true})
source: new VectorSource({
wrapX: true,
loader: loader,
strategy: bboxStrategy
})
});
renderer = new CanvasVectorLayerRenderer(layer);
const projection = getProjection('EPSG:3857');
projExtent = projection.getExtent();
worldWidth = getWidth(projExtent);
buffer = layer.getRenderBuffer();
loadExtents = [];
frameState = {
viewHints: [],
viewState: {
center: [0, 0],
projection: projection,
resolution: 1,
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() {
frameState.extent =
[projExtent[0] - 10000, -10000, projExtent[0] + 10000, 10000];
setExtent([projExtent[0] - 10000, -10000, projExtent[0] + 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(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() {
frameState.extent =
[projExtent[0] - 10000, -10000, projExtent[1] - 10000, 10000];
setExtent([projExtent[0] - 10000, -10000, projExtent[2] - 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(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() {
frameState.extent =
[2 * projExtent[0] - 10000, -10000, 2 * projExtent[1] + 10000, 10000];
setExtent([2 * projExtent[0] + 10000, -10000, 2 * projExtent[2] - 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);
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 = [
projExtent[0] - 2 * worldWidth - 10000,
-10000, projExtent[1] + 2 * worldWidth + 10000, 10000
];
setExtent([projExtent[0] - 2 * worldWidth - 10000,
-10000, projExtent[0] + 2 * worldWidth + 10000, 10000
]);
renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(bufferExtent([
projExtent[0] - 2 * worldWidth - 10000,
-10000, projExtent[2] + 2 * worldWidth + 10000, 10000
], 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() {
frameState.extent = [-10000, -10000, 10000, 10000];
setExtent([-10000, -10000, 10000, 10000]);
renderer.prepareFrame(frameState);
expect(renderer.replayGroupChanged).to.be(true);
renderer.prepareFrame(frameState);

View File

@@ -1,4 +1,5 @@
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 Projection from '../../../../src/ol/proj/Projection.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');
});
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() {
const tmpXml = content.replace(/<ows:Constraint[\s\S]*?<\/ows:Constraint>/g, '');
const tmpCapabilities = parser.read(tmpXml);

View File

@@ -3,9 +3,10 @@
/* Basic Options */
"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'. */
"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. */
"checkJs": true, /* Report errors in .js files. */
"skipLibCheck": true,
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */