Compare commits

..

80 Commits

Author SHA1 Message Date
Olivier Guyot
2ad39cf69e Added missing Type component 2019-09-24 10:50:52 +02:00
Olivier Guyot
6929cb3001 Store typedef on the helper and use it to show a parameter list 2019-09-24 10:47:06 +02:00
Tim Schaub
5ee3063d01 Gatsby setup for API docs 2019-05-20 10:30:44 -06:00
Frédéric Junod
2322131b01 Merge pull request #9576 from fredj/has-geolocation
Remove geolocation detection from ol/has
2019-05-18 09:40:21 +02:00
Frederic Junod
39bb6a8ffe Remove geolocation detection from ol/has 2019-05-18 08:50:33 +02:00
Tim Schaub
0631a121c3 Merge pull request #9575 from tschaub/untwisted-exports
Rework exports for getUid and control defaults
2019-05-17 13:03:14 -06:00
Tim Schaub
c860f15f91 Break the circular dependency by extending a pluggable map 2019-05-17 12:38:32 -06:00
Tim Schaub
c10fd7aaab Avoid overwriting longname for getUid 2019-05-17 12:00:10 -06:00
Andreas Hocevar
7ced251d19 Merge pull request #9574 from jumpinjackie/update-mapguide-demo-server
Update OL MapGuide example to point to my demo MapGuide Server.
2019-05-17 19:49:39 +02:00
Frédéric Junod
637c823fa6 Merge pull request #9571 from fredj/misc_examples
Small examples improvements
2019-05-17 17:00:58 +02:00
Frederic Junod
2d655143c3 Add an Overlay in the export-map example 2019-05-17 16:45:29 +02:00
Jackie Ng
fba95ea3f0 Update OL MapGuide example to point to my demo MapGuide Server.
Fixes #9573
2019-05-18 00:21:53 +10:00
Frédéric Junod
40393cd9a4 Merge pull request #9572 from fredj/upgrade-notes_css
Add a note about CSS removal in upgrade-notes.md
2019-05-17 15:44:31 +02:00
Frederic Junod
10527bd221 Remove margin in full screen
In only affects Safari, in Chrome `margin: 0;` is in the default browser's style sheet.
2019-05-17 15:41:03 +02:00
Frederic Junod
4c2b11f6d7 Add a note about CSS removal in upgrade-notes.md 2019-05-17 15:21:51 +02:00
Frédéric Junod
512a39bafd Merge pull request #9570 from fredj/rm_media_print
Remove CSS print rules in ol.css
2019-05-17 14:58:55 +02:00
Frederic Junod
78c09a0ff6 Add svg icon example 2019-05-17 14:48:03 +02:00
Frederic Junod
6dc00b75e0 Update d3 version in d3 example 2019-05-17 14:27:20 +02:00
Frederic Junod
6b50f1555b Add webgl tag to heatmap layer example 2019-05-17 14:25:34 +02:00
Frederic Junod
a6f098f78b Remove CSS print rules in ol.css
Fixes #7909
2019-05-17 14:21:19 +02:00
Tim Schaub
6d06f81ca7 Merge pull request #9563 from tschaub/fragile-docs
Fix @abstract and @module annotations
2019-05-16 16:27:09 -06:00
Tim Schaub
908ecb39e3 Description below @abstract trips up JSDoc 2019-05-16 16:10:30 -06:00
Tim Schaub
6cad19e9d5 Description below @module trips up JSDoc 2019-05-16 16:09:30 -06:00
Andreas Hocevar
17d26acb2f Merge pull request #9560 from ahocevar/simplify-loading
Simplify loading detection loop
2019-05-16 22:27:59 +02:00
Andreas Hocevar
579fadd797 Merge pull request #9561 from ahocevar/map-memory-leak
Remove memory leak caused by label cache listeners
2019-05-16 22:26:56 +02:00
Tim Schaub
9693336d99 Merge pull request #9559 from tschaub/minify-worker
Minify worker
2019-05-16 14:20:11 -06:00
ahocevar
335648d613 Remove memory leak caused by label cache listeners 2019-05-16 21:04:55 +02:00
ahocevar
dc906f79e1 Simplify loading detection loop 2019-05-16 20:19:32 +02:00
Tim Schaub
34a8a484c4 Merge pull request #9558 from tschaub/unlisten
Stop listening for image decoding
2019-05-16 11:46:15 -06:00
Tim Schaub
0d489f2ea9 Minify worker in examples for a production build 2019-05-16 10:41:33 -06:00
Tim Schaub
d49e166506 Minify worker source 2019-05-16 10:35:18 -06:00
Frederic Junod
a0e6af425e Add tests for listenImage function, fix private variables initialization 2019-05-16 16:42:37 +02:00
Tim Schaub
ee9a0bcd05 Stop listening for image decoding 2019-05-16 08:06:12 -06:00
Tim Schaub
442fbb13d2 Merge pull request #9550 from tschaub/worker
Setup for building workers
2019-05-16 07:02:34 -06:00
Frédéric Junod
fddc5bcc5b Merge pull request #9555 from fredj/rm_unused
Remove unused roundUpToPowerOfTwo function
2019-05-16 13:57:19 +02:00
Frederic Junod
c2058af13a Remove unused roundUpToPowerOfTwo function 2019-05-16 12:11:58 +02:00
Frederic Junod
bb022050ed Add 'webworker' lib into the TypeScript's compilerOptions 2019-05-16 09:49:29 +02:00
Frédéric Junod
a8e5cb1e12 Merge pull request #9553 from openlayers/greenkeeper/ol-mapbox-style-5.0.0-beta.2
chore(package): update ol-mapbox-style to version 5.0.0-beta.2
2019-05-16 09:43:56 +02:00
Frédéric Junod
1628ce8729 Merge pull request #9551 from fredj/img_decode
Use HTMLImageElement.decode if available
2019-05-16 09:30:55 +02:00
greenkeeper[bot]
37f96ddcfa chore(package): update ol-mapbox-style to version 5.0.0-beta.2 2019-05-16 07:21:23 +00:00
Andreas Hocevar
1fc75fdc68 Merge pull request #9500 from edellucien/kml_extended_data
KML string validation with extendedData
2019-05-16 09:20:03 +02:00
lucien
10c4ec0b37 Only use 'pushSerializeAndPop' later in code to write extendedData after 2019-05-16 09:10:40 +02:00
Frederic Junod
2e34dd0faf Use HTMLImageElement.decode if available 2019-05-16 08:53:29 +02:00
Tim Schaub
b7b37f9548 Add worker loader to the tests 2019-05-15 16:49:54 -06:00
Tim Schaub
05f13bb363 Add worker loader to the examples 2019-05-15 16:49:38 -06:00
Tim Schaub
e307410301 A task to inline workers 2019-05-15 16:38:06 -06:00
Tim Schaub
5a4541dadb Merge pull request #9534 from tschaub/simpler-mapbox-layer-example
Remove unnecessary parts from the Mapbox layer example
2019-05-15 14:00:46 -06:00
Andreas Hocevar
56ec6b093e Merge pull request #9548 from ahocevar/italic-labels
Avoid cut off labels for italic fonts
2019-05-15 16:58:17 +02:00
Frédéric Junod
61d753c803 Merge pull request #9549 from fredj/rm_unused
Remove unused getReplayTransform_ function in VectorTileLayer
2019-05-15 16:31:09 +02:00
Tim Schaub
f312706269 Remove unnecessary parts from the Mapbox layer example 2019-05-15 08:26:43 -06:00
Frederic Junod
f33ad5e025 Remove unused getReplayTransform_ function in VectorTileLayer 2019-05-15 15:10:54 +02:00
ahocevar
2e31f716ed Avoid cut off labels for italic text 2019-05-15 15:01:12 +02:00
Tim Schaub
eafb657264 Merge pull request #9545 from tschaub/default-view-extent
Avoid panning off the edge of the world
2019-05-15 04:58:29 -06:00
Tim Schaub
0cee259d0b Merge pull request #9539 from tschaub/event-error
Document existing error event type
2019-05-15 04:53:57 -06:00
Tim Schaub
be8797f355 Merge pull request #9541 from tschaub/rbush-three
Upgrade to rbush@3
2019-05-15 04:53:15 -06:00
Tim Schaub
d652bfc4a7 Merge pull request #9543 from tschaub/upgrade-jsdoc-plugin
Update jsdoc-plugin-typescript to version 2.0.1
2019-05-15 04:52:26 -06:00
Andreas Hocevar
11607caa81 Merge pull request #9536 from ahocevar/font-cache-hits
More font cache hits
2019-05-15 12:13:40 +02:00
ahocevar
41e958ea1f Split text into single chars or don't split at all 2019-05-15 11:55:29 +02:00
Olivier Guyot
39de2451bc Merge pull request #9537 from jahow/webgl-geojson-refactoring
WebGL / Points renderer refactoring
2019-05-15 11:44:40 +02:00
Olivier Guyot
e11e3c5f6e Webgl / minor function renaming 2019-05-15 11:30:24 +02:00
Olivier Guyot
c705775d75 Linting 2019-05-15 10:20:31 +02:00
Olivier Guyot
9ca75e9d43 Webgl points / rebuild buffers when source change 2019-05-15 10:18:53 +02:00
Olivier Guyot
5d2b7fe4bb Webgl points / use the helper ton compute the projection transform
The `WebGLHelper` class now provides a `makeProjectionTransform` method
that updates a transform to match the projection for a given frame state.

This also means that the WebGLHelper does not set the projection matrix
uniform anymore, this is the responsibility of the renderer as
the rendered coordinates will not be in world space from now on.
2019-05-15 10:18:47 +02:00
Olivier Guyot
fb455891ce Webgl points / make coordinates in view space to avoid precision glitches
Fixes #9479
2019-05-15 10:18:47 +02:00
Olivier Guyot
3e2e45ce6d Webgl helper / add the possibility to use transforms as custom uniforms
These will be translated into 4x4 matrices in GLSL.
2019-05-15 10:18:47 +02:00
Olivier Guyot
75eb62363a WebGL points / rebuild buffers only when extent changed 2019-05-15 10:18:47 +02:00
Olivier Guyot
523097903a WebGL points / rebuild buffers on every non animation frame 2019-05-15 10:18:22 +02:00
Olivier Guyot
c6d214b585 Webgl / implement support for custom attributes in webgl buffers
Only numerical attributes can be pushed in webgl buffers.
2019-05-15 10:15:55 +02:00
Frédéric Junod
c9604dbd69 Merge pull request #9546 from fredj/canvas_optim
Lazily create the hit detection context
2019-05-15 10:15:02 +02:00
Frédéric Junod
df37519858 Initialize hitDetectionContext to null
Co-Authored-By: Andreas Hocevar <andreas.hocevar@gmail.com>
2019-05-15 10:03:43 +02:00
Frederic Junod
10c6009ffa Lazily create the hit detection context 2019-05-15 09:52:57 +02:00
Olivier Guyot
27e520add4 WebGL points / use pushFeatureInBuffer util
Feature objects are converted to geoJSON with the properties
set for rendering.
2019-05-15 09:47:00 +02:00
Olivier Guyot
3a429d3f6c Webgl / add utils for pushing geojson geometries in webgl buffers
For now only point geometries are handled.
2019-05-15 09:44:41 +02:00
Olivier Guyot
5e36468245 Webgl / add a simple Layer renderer for common logic
This will eventually provides utilities for manipulating & rendering data
2019-05-15 09:43:29 +02:00
Olivier Guyot
a2d83f5358 WebGL points / add minimal tests 2019-05-15 09:42:15 +02:00
Tim Schaub
217c6ba764 Avoid panning off the edge of the world 2019-05-14 20:15:51 -06:00
greenkeeper[bot]
039f21274b chore(package): update jsdoc-plugin-typescript to version 2.0.1 2019-05-14 18:59:50 -06:00
Tim Schaub
09fdd30876 Upgrade to rbush@3 2019-05-14 18:43:08 -06:00
Tim Schaub
811e5f60eb Document existing error event type 2019-05-14 14:28:00 -06:00
lucien
81f99f1579 Write placemark's ExtendedData tag after Style tag 2019-05-07 09:02:28 +02:00
89 changed files with 22810 additions and 622 deletions

View File

@@ -50,3 +50,11 @@ jobs:
- store_artifacts: - store_artifacts:
path: build/apidoc path: build/apidoc
destination: apidoc destination: apidoc
- run:
name: Build Website
command: npm run build-site
- store_artifacts:
path: public
destination: website

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@
/coverage/ /coverage/
/dist/ /dist/
node_modules/ node_modules/
/.cache/
/public/

View File

@@ -4,6 +4,28 @@
#### Backwards incompatible changes #### Backwards incompatible changes
#### Removal of `GEOLOCATION` constant from `ol/has`
If you were previously using this constant, you can check if `'geolocation'` is define in `navigator` instead.
```js
if ('geolocation' in navigator) {
// ...
}
```
#### Removal of CSS print rules
The CSS media print rules were removed from the `ol.css` file. To get the previous behavior, use the following CSS:
```css
@media print {
.ol-control {
display: none;
}
}
```
#### Removal of optional this arguments #### Removal of optional this arguments
The optional this (i.e. opt_this) arguments were removed from the following methods. The optional this (i.e. opt_this) arguments were removed from the following methods.
@@ -32,9 +54,9 @@ Previously, this options only constrained the view *center*. This behaviour can
As a side effect, the view `rotate` method is gone and has been replaced with `adjustRotation` which takes a delta as input. As a side effect, the view `rotate` method is gone and has been replaced with `adjustRotation` which takes a delta as input.
##### Zoom is constrained so only one world is visible ##### The view is constrained so only one world is visible
Previously, maps showed multiple worlds at low zoom levels. Now, the view is restricted to show only one world. To get the previous behavior, configure the `ol/View` with `multiWorld: true`. Previously, maps showed multiple worlds at low zoom levels. In addition, it used to be possible to pan off the north or south edge of the world. Now, the view is restricted to show only one world, and you cannot pan off the edge. To get the previous behavior, configure the `ol/View` with `multiWorld: true`.
##### Removal of deprecated methods ##### Removal of deprecated methods

View File

@@ -0,0 +1,23 @@
{
"opts": {
"recurse": true,
"template": "node_modules/jsdoc-json"
},
"tags": {
"allowUnknownTags": true
},
"source": {
"includePattern": "\\.js$",
"include": [
"src/ol"
]
},
"plugins": [
"jsdoc-plugin-typescript",
"config/jsdoc/api-info/plugins/api",
"config/jsdoc/api-info/plugins/module"
],
"typescript": {
"moduleRoot": "src"
}
}

View File

@@ -0,0 +1,15 @@
/**
* Handle the api annotation.
* @param {Object} dictionary The tag dictionary.
*/
exports.defineTags = dictionary => {
dictionary.defineTag('api', {
onTagged: (doclet, tag) => {
doclet.api = true;
}
});
};

View File

@@ -0,0 +1,170 @@
/**
* This plugin adds an `exportMap` property to @module doclets. Each export map
* is an object with properties named like the local identifier and values named
* like the exported identifier.
*
* For example, the code below
*
* export {foo as bar};
*
* would be a map like `{foo: 'bar'}`.
*
* In the case of an export declaration with a source, the export identifier is
* prefixed by the source. For example, this code
*
* export {foo as bar} from 'ol/bam';
*
* would be a map like `{'ol/bam foo': 'bar'}`.
*
* If a default export is a literal or object expression, the local name will be
* an empty string. For example
*
* export default {foo: 'bar'};
*
* would be a map like `{'': 'default'}`.
*/
const assert = require('assert');
const path = require('path');
/**
* A lookup of export maps per source filepath.
*/
const exportMapLookup = {};
function loc(filepath, node) {
return `${filepath}:${node.loc.start.line}`;
}
function nameFromChildIdentifier(filepath, node) {
assert.ok(node.id, `expected identifer in ${loc(filepath, node)}`);
assert.strictEqual(node.id.type, 'Identifier', `expected identifer in ${loc(filepath, node)}`);
return node.id.name;
}
function handleExportNamedDeclaration(filepath, node) {
if (!(filepath in exportMapLookup)) {
exportMapLookup[filepath] = {};
}
const exportMap = exportMapLookup[filepath];
const declaration = node.declaration;
if (declaration) {
// `export class Foo{}` or `export function foo() {}`
if (declaration.type === 'ClassDeclaration' || declaration.type === 'FunctionDeclaration') {
const name = nameFromChildIdentifier(filepath, declaration);
exportMap[name] = name;
return;
}
// `export const foo = 'bar', bam = 42`
if (declaration.type === 'VariableDeclaration') {
const declarations = declaration.declarations;
assert.ok(declarations.length > 0, `expected variable declarations in ${loc(filepath, declaration)}`);
for (const declarator of declarations) {
assert.strictEqual(declarator.type, 'VariableDeclarator', `unexpected "${declarator.type}" in ${loc(filepath, declarator)}`);
const name = nameFromChildIdentifier(filepath, declarator);
exportMap[name] = name;
}
return;
}
throw new Error(`Unexpected named export "${declaration.type}" in ${loc(filepath, declaration)}`);
}
let prefix = '';
const source = node.source;
if (source) {
// `export foo from 'bar'`
assert.strictEqual(source.type, 'Literal', `unexpected export source "${source.type}" in ${loc(filepath, source)}`);
prefix = `${source.value} `;
}
const specifiers = node.specifiers;
assert.ok(specifiers.length > 0, `expected export specifiers in ${loc(filepath, node)}`);
// `export {foo, bar}` or `export {default as Foo} from 'bar'`
for (const specifier of specifiers) {
assert.strictEqual(specifier.type, 'ExportSpecifier', `unexpected export specifier in ${loc(filepath, specifier)}`);
const local = specifier.local;
assert.strictEqual(local.type, 'Identifier', `unexpected local specifier "${local.type} in ${loc(filepath, local)}`);
const exported = specifier.exported;
assert.strictEqual(local.type, 'Identifier', `unexpected exported specifier "${exported.type} in ${loc(filepath, exported)}`);
exportMap[prefix + local.name] = exported.name;
}
}
function handleDefaultDeclaration(filepath, node) {
if (!(filepath in exportMapLookup)) {
exportMapLookup[filepath] = {};
}
const exportMap = exportMapLookup[filepath];
const declaration = node.declaration;
if (declaration) {
// `export default class Foo{}` or `export default function foo () {}`
if (declaration.type === 'ClassDeclaration' || declaration.type === 'FunctionDeclaration') {
const name = nameFromChildIdentifier(filepath, declaration);
exportMap[name] = 'default';
return;
}
// `export default foo`
if (declaration.type === 'Identifier') {
exportMap[declaration.name] = 'default';
return;
}
// `export default {foo: 'bar'}` or `export default 42`
if (declaration.type === 'ObjectExpression' || declaration.type === 'Literal') {
exportMap[''] = 'default';
return;
}
}
throw new Error(`Unexpected default export "${declaration.type}" in ${loc(filepath, declaration)}`);
}
exports.astNodeVisitor = {
visitNode: (node, event, parser, filepath) => {
if (node.type === 'ExportNamedDeclaration') {
return handleExportNamedDeclaration(filepath, node);
}
if (node.type === 'ExportDefaultDeclaration') {
return handleDefaultDeclaration(filepath, node);
}
}
};
const moduleLookup = {};
exports.handlers = {
// create a lookup of @module doclets
newDoclet: event => {
const doclet = event.doclet;
if (doclet.kind === 'module') {
const filepath = path.join(doclet.meta.path, doclet.meta.filename);
assert.ok(!(filepath in moduleLookup), `duplicate @module doc in ${filepath}`);
moduleLookup[filepath] = doclet;
}
},
// assign the `exportMap` property to @module doclets
parseComplete: event => {
for (const filepath in moduleLookup) {
assert.ok(filepath in exportMapLookup, `missing ${filepath} in export map lookup`);
moduleLookup[filepath].exportMap = exportMapLookup[filepath];
}
// make sure there was a @module doclet for each export map
for (const filepath in exportMapLookup) {
assert.ok(filepath in moduleLookup, `missing @module doclet in ${filepath}`);
}
}
};

View File

@@ -6,7 +6,7 @@ docs: >
The example loads TopoJSON geometries and uses d3 (<code>d3.geo.path</code>) to render these geometries to a SVG element. The example loads TopoJSON geometries and uses d3 (<code>d3.geo.path</code>) to render these geometries to a SVG element.
tags: "d3" tags: "d3"
resources: resources:
- https://unpkg.com/d3@4.12.0/build/d3.js - https://unpkg.com/d3@5.9.2/dist/d3.js
- https://unpkg.com/topojson@3.0.2/dist/topojson.js - https://unpkg.com/topojson@3.0.2/dist/topojson.js
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>

2
examples/d3.js vendored
View File

@@ -85,7 +85,7 @@ const map = new Map({
/** /**
* Load the topojson data and create an ol/layer/Image for that data. * Load the topojson data and create an ol/layer/Image for that data.
*/ */
d3.json('data/topojson/us.json', function(error, us) { d3.json('data/topojson/us.json').then(function(us) {
const layer = new CanvasLayer({ const layer = new CanvasLayer({
features: topojson.feature(us, us.objects.counties) features: topojson.feature(us, us.objects.counties)

7
examples/data/square.svg Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg">
<g>
<rect width="20" height="20" style="fill:#fff" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 194 B

6
examples/export-map.css Normal file
View File

@@ -0,0 +1,6 @@
.overlay {
background-color: yellow;
border-radius: 6px;
padding: 4px;
white-space: nowrap;
}

View File

@@ -8,5 +8,8 @@ docs: >
tags: "export, png, openstreetmap" tags: "export, png, openstreetmap"
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>
<div style="display: none;">
<div class="overlay" id="null">Null Island</div>
</div>
<a id="export-png" class="btn btn-default"><i class="fa fa-download"></i> Download PNG</a> <a id="export-png" class="btn btn-default"><i class="fa fa-download"></i> Download PNG</a>
<a id="image-download" download="map.png"></a> <a id="image-download" download="map.png"></a>

View File

@@ -1,5 +1,6 @@
import Map from '../src/ol/Map.js'; import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js'; import View from '../src/ol/View.js';
import Overlay from '../src/ol/Overlay.js';
import GeoJSON from '../src/ol/format/GeoJSON.js'; import GeoJSON from '../src/ol/format/GeoJSON.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
import {OSM, Vector as VectorSource} from '../src/ol/source.js'; import {OSM, Vector as VectorSource} from '../src/ol/source.js';
@@ -25,11 +26,17 @@ const map = new Map({
}) })
}); });
map.addOverlay(new Overlay({
position: [0, 0],
element: document.getElementById('null')
}));
// export options for html-to-image. // export options for html-to-image.
// See: https://github.com/bubkoo/html-to-image#options // See: https://github.com/bubkoo/html-to-image#options
const exportOptions = { const exportOptions = {
filter: function(element) { filter: function(element) {
return element.className.indexOf('ol-control') === -1; return element.className ? element.className.indexOf('ol-control') === -1 : true;
} }
}; };

View File

@@ -2,7 +2,7 @@ import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js'; import View from '../src/ol/View.js';
import TileLayer from '../src/ol/layer/Tile.js'; import TileLayer from '../src/ol/layer/Tile.js';
import OSM from '../src/ol/source/OSM.js'; import OSM from '../src/ol/source/OSM.js';
import {defaults as defaultControls} from '../src/ol/control/util'; import {defaults as defaultControls} from '../src/ol/control.js';
import ZoomSlider from '../src/ol/control/ZoomSlider'; import ZoomSlider from '../src/ol/control/ZoomSlider';
const view = new View({ const view = new View({

View File

@@ -11,7 +11,6 @@ import {clamp, lerp} from '../src/ol/math';
import Stamen from '../src/ol/source/Stamen'; import Stamen from '../src/ol/source/Stamen';
const vectorSource = new Vector({ const vectorSource = new Vector({
features: [],
attributions: 'NASA' attributions: 'NASA'
}); });

View File

@@ -1,5 +1,6 @@
.map:-webkit-full-screen { .map:-webkit-full-screen {
height: 100%; height: 100%;
margin: 0;
} }
.map:-ms-fullscreen { .map:-ms-fullscreen {
height: 100%; height: 100%;

View File

@@ -1,5 +1,6 @@
.fullscreen:-webkit-full-screen { .fullscreen:-webkit-full-screen {
height: 100%; height: 100%;
margin: 0;
} }
.fullscreen:-ms-fullscreen { .fullscreen:-ms-fullscreen {
height: 100%; height: 100%;

View File

@@ -1,5 +1,6 @@
.map:-webkit-full-screen { .map:-webkit-full-screen {
height: 100%; height: 100%;
margin: 0;
} }
.map:-ms-fullscreen { .map:-ms-fullscreen {
height: 100%; height: 100%;

View File

@@ -4,7 +4,7 @@ title: Earthquakes Heatmap
shortdesc: Demonstrates the use of a heatmap layer. shortdesc: Demonstrates the use of a heatmap layer.
docs: > docs: >
This example parses a KML file and renders the features as a <code>ol/layer/Heatmap</code> layer. This example parses a KML file and renders the features as a <code>ol/layer/Heatmap</code> layer.
tags: "heatmap, kml, vector, style" tags: "heatmap, kml, vector, style, webgl"
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>
<form> <form>

View File

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

View File

@@ -1,12 +1,68 @@
import Map from '../src/ol/Map.js'; import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js'; import View from '../src/ol/View.js';
import Layer from '../src/ol/layer/Layer'; import Layer from '../src/ol/layer/Layer';
import {toLonLat} from '../src/ol/proj'; import {toLonLat, fromLonLat} from '../src/ol/proj';
import {Stroke, Style} from '../src/ol/style.js'; import {Stroke, Style} from '../src/ol/style.js';
import VectorLayer from '../src/ol/layer/Vector.js'; import VectorLayer from '../src/ol/layer/Vector.js';
import VectorSource from '../src/ol/source/Vector.js'; import VectorSource from '../src/ol/source/Vector.js';
import GeoJSON from '../src/ol/format/GeoJSON.js'; import GeoJSON from '../src/ol/format/GeoJSON.js';
const center = [-98.8, 37.9];
const key = 'ER67WIiPdCQvhgsUjoWK';
const mbMap = new mapboxgl.Map({
style: 'https://maps.tilehosting.com/styles/bright/style.json?key=' + key,
attributionControl: false,
boxZoom: false,
center: center,
container: 'map',
doubleClickZoom: false,
dragPan: false,
dragRotate: false,
interactive: false,
keyboard: false,
pitchWithRotate: false,
scrollZoom: false,
touchZoomRotate: false
});
const mbLayer = new Layer({
render: function(frameState) {
const canvas = mbMap.getCanvas();
const viewState = frameState.viewState;
const visible = mbLayer.getVisible();
canvas.style.display = visible ? 'block' : 'none';
const opacity = mbLayer.getOpacity();
canvas.style.opacity = opacity;
// adjust view parameters in mapbox
const rotation = viewState.rotation;
if (rotation) {
mbMap.rotateTo(-rotation * 180 / Math.PI, {
animate: false
});
}
mbMap.jumpTo({
center: toLonLat(viewState.center),
zoom: viewState.zoom - 1,
animate: false
});
// cancel the scheduled update & trigger synchronous redraw
// see https://github.com/mapbox/mapbox-gl-js/issues/7893#issue-408992184
// NOTE: THIS MIGHT BREAK WHEN UPDATING MAPBOX
if (mbMap._frame) {
mbMap._frame.cancel();
mbMap._frame = null;
}
mbMap._render();
return canvas;
}
});
const style = new Style({ const style = new Style({
stroke: new Stroke({ stroke: new Stroke({
color: '#319FD3', color: '#319FD3',
@@ -25,80 +81,8 @@ const vectorLayer = new VectorLayer({
const map = new Map({ const map = new Map({
target: 'map', target: 'map',
view: new View({ view: new View({
center: [-10997148, 4569099], center: fromLonLat(center),
zoom: 4, zoom: 4
minZoom: 1, }),
extent: [-Infinity, -20048966.10, Infinity, 20048966.10], layers: [mbLayer, vectorLayer]
smoothExtentConstraint: false,
smoothResolutionConstraint: false
})
}); });
// init Mapbox object
const view = map.getView();
const center = toLonLat(view.getCenter(), view.getProjection());
const key = 'ER67WIiPdCQvhgsUjoWK';
const mbMap = new mapboxgl.Map({
style: 'https://maps.tilehosting.com/styles/bright/style.json?key=' + key,
attributionControl: false,
boxZoom: false,
center: center,
container: map.getTargetElement(),
doubleClickZoom: false,
dragPan: false,
dragRotate: false,
interactive: false,
keyboard: false,
pitchWithRotate: false,
scrollZoom: false,
touchZoomRotate: false,
zoom: view.getZoom() - 1
});
// init OL layers
const mbLayer = new Layer({
render: function(frameState) {
const canvas = mbMap.getCanvas();
const view = map.getView();
const visible = mbLayer.getVisible();
canvas.style.display = visible ? 'block' : 'none';
const opacity = mbLayer.getOpacity();
canvas.style.opacity = opacity;
// adjust view parameters in mapbox
const rotation = frameState.viewState.rotation;
if (rotation) {
mbMap.rotateTo(-rotation * 180 / Math.PI, {
animate: false
});
}
const center = toLonLat(view.getCenter(), view.getProjection());
const zoom = view.getZoom() - 1;
mbMap.jumpTo({
center: center,
zoom: zoom,
animate: false
});
// cancel the scheduled update & trigger synchronous redraw
// see https://github.com/mapbox/mapbox-gl-js/issues/7893#issue-408992184
// NOTE: THIS MIGHT BREAK WHEN UPDATING MAPBOX
if (mbMap._frame) {
mbMap._frame.cancel();
mbMap._frame = null;
}
mbMap._render();
return canvas;
}
});
map.addLayer(mbLayer);
map.addLayer(vectorLayer);

View File

@@ -3,9 +3,9 @@ import View from '../src/ol/View.js';
import ImageLayer from '../src/ol/layer/Image.js'; import ImageLayer from '../src/ol/layer/Image.js';
import ImageMapGuide from '../src/ol/source/ImageMapGuide.js'; import ImageMapGuide from '../src/ol/source/ImageMapGuide.js';
const mdf = 'Library://Public/Samples/Sheboygan/Maps/Sheboygan.MapDefinition'; const mdf = 'Library://Samples/Sheboygan/Maps/Sheboygan.MapDefinition';
const agentUrl = const agentUrl =
'http://www.buoyshark.com/mapguide/mapagent/mapagent.fcgi?'; 'http://138.197.230.93:8008/mapguide/mapagent/mapagent.fcgi?';
const bounds = [ const bounds = [
-87.865114442365922, -87.865114442365922,
43.665065564837931, 43.665065564837931,
@@ -24,8 +24,9 @@ const map = new Map({
params: { params: {
MAPDEFINITION: mdf, MAPDEFINITION: mdf,
FORMAT: 'PNG', FORMAT: 'PNG',
USERNAME: 'OpenLayers', VERSION: '3.0.0',
PASSWORD: 'OpenLayers' USERNAME: 'OLGuest',
PASSWORD: 'olguest'
}, },
ratio: 2 ratio: 2
}) })

View File

@@ -19,16 +19,25 @@ module.exports = {
context: src, context: src,
target: 'web', target: 'web',
entry: entry, entry: entry,
stats: 'minimal',
module: { module: {
rules: [{ rules: [{
test: /\.js$/,
use: { use: {
loader: 'buble-loader' loader: 'buble-loader'
}, },
test: /\.js$/,
include: [ include: [
path.join(__dirname, '..', '..', 'src'), path.join(__dirname, '..', '..', 'src'),
path.join(__dirname, '..') path.join(__dirname, '..')
] ]
}, {
test: /\.js$/,
use: {
loader: path.join(__dirname, './worker-loader.js')
},
include: [
path.join(__dirname, '../../src/ol/worker')
]
}] }]
}, },
optimization: { optimization: {

View File

@@ -0,0 +1,24 @@
const build = require('../../tasks/serialize-workers').build;
function loader() {
const callback = this.async();
let minify = false;
// TODO: remove when https://github.com/webpack/webpack/issues/6496 is addressed
const compilation = this._compilation;
if (compilation) {
minify = compilation.compiler.options.mode === 'production';
}
build(this.resource, {minify})
.then(chunk => {
for (const filePath in chunk.modules) {
this.addDependency(filePath);
}
callback(null, chunk.code);
})
.catch(callback);
}
module.exports = loader;

10
examples/worker.html Normal file
View File

@@ -0,0 +1,10 @@
---
layout: example.html
title: Worker
shortdesc: This example should be deleted.
docs: >
When you move the map, a message is sent to a worker. In response, the woker sends a
message back with the version identifier.
tags: "worker"
---
<div id="map" class="map"></div>

35
examples/worker.js Normal file
View File

@@ -0,0 +1,35 @@
/* eslint-disable no-console */
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import TileLayer from '../src/ol/layer/Tile.js';
import OSM from '../src/ol/source/OSM.js';
import {create as createVersionWorker} from '../src/ol/worker/version';
const map = new Map({
layers: [
new TileLayer({
source: new OSM()
})
],
target: 'map',
view: new View({
center: [0, 0],
zoom: 2
})
});
const worker = createVersionWorker();
worker.addEventListener('error', function(error) {
console.error('worker error', error);
});
worker.addEventListener('message', function(event) {
console.log('message from worker:', event.data);
});
map.on('moveend', function(event) {
const state = event.frameState.viewState;
worker.postMessage({zoom: state.zoom, center: state.center});
});

11
gatsby-config.js Normal file
View File

@@ -0,0 +1,11 @@
module.exports = {
plugins: [
'gatsby-plugin-emotion',
{
resolve: 'gatsby-plugin-typography',
options: {
pathToConfigModule: 'site/util/typography'
}
}
]
};

23
gatsby-node.js Normal file
View File

@@ -0,0 +1,23 @@
function getDocs() {
// TODO: build if not present
const info = require('./build/api-info.json');
return info.docs.filter(doc => !doc.ignore && (doc.api || doc.kind === 'module' || doc.kind === 'typedef'));
}
function createPages({actions: {createPage}}) {
createPage({
path: '/api',
component: require.resolve('./site/pages/API.js'),
context: {docs: getDocs()}
});
createPage({
path: '/info',
component: require.resolve('./site/pages/Info.js'),
context: {docs: getDocs()}
});
}
exports.createPages = createPages;

20668
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -22,9 +22,12 @@
"build-index": "npm run build-package && node tasks/generate-index", "build-index": "npm run build-package && node tasks/generate-index",
"build-legacy": "shx rm -rf build && npm run build-index && webpack --config config/webpack-config-legacy-build.js && cleancss --source-map src/ol/ol.css -o build/legacy/ol.css", "build-legacy": "shx rm -rf build && npm run build-index && webpack --config config/webpack-config-legacy-build.js && cleancss --source-map src/ol/ol.css -o build/legacy/ol.css",
"copy-css": "shx cp src/ol/ol.css build/ol/ol.css", "copy-css": "shx cp src/ol/ol.css build/ol/ol.css",
"transpile": "shx rm -rf build/ol && shx mkdir -p build/ol && shx cp -rf src/ol build/ol/src && tsc --project config/tsconfig-build.json", "transpile": "shx rm -rf build/ol && shx mkdir -p build/ol && shx cp -rf src/ol build/ol/src && node tasks/serialize-workers && tsc --project config/tsconfig-build.json",
"typecheck": "tsc --pretty", "typecheck": "tsc --pretty",
"apidoc": "jsdoc -R config/jsdoc/api/index.md -c config/jsdoc/api/conf.json -P package.json -d build/apidoc" "apidoc": "jsdoc -R config/jsdoc/api/index.md -c config/jsdoc/api/conf.json -P package.json -d build/apidoc",
"api-info": "jsdoc --configure config/jsdoc/api-info/conf.json --destination build/api-info.json",
"serve-site": "gatsby develop",
"build-site": "npm run api-info && gatsby build"
}, },
"main": "index.js", "main": "index.js",
"repository": { "repository": {
@@ -38,16 +41,17 @@
"dependencies": { "dependencies": {
"pbf": "3.2.0", "pbf": "3.2.0",
"pixelworks": "1.1.0", "pixelworks": "1.1.0",
"rbush": "2.0.2" "rbush": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.4.0", "@babel/core": "^7.4.0",
"@babel/preset-env": "^7.4.2", "@babel/preset-env": "^7.4.4",
"@emotion/core": "^10.0.10",
"@emotion/styled": "^10.0.11",
"@openlayers/eslint-plugin": "^4.0.0-beta.2", "@openlayers/eslint-plugin": "^4.0.0-beta.2",
"@types/arcgis-rest-api": "^10.4.4", "@types/arcgis-rest-api": "^10.4.4",
"@types/geojson": "^7946.0.7", "@types/geojson": "^7946.0.7",
"@types/pbf": "^3.0.1", "@types/pbf": "^3.0.1",
"@types/rbush": "^2.0.2",
"@types/topojson-specification": "^1.0.1", "@types/topojson-specification": "^1.0.1",
"babel-loader": "^8.0.5", "babel-loader": "^8.0.5",
"buble": "^0.19.7", "buble": "^0.19.7",
@@ -58,9 +62,14 @@
"coveralls": "3.0.3", "coveralls": "3.0.3",
"eslint": "^5.16.0", "eslint": "^5.16.0",
"eslint-config-openlayers": "^11.0.0", "eslint-config-openlayers": "^11.0.0",
"eslint-config-tschaub": "^13.1.0",
"eslint-plugin-react": "^7.13.0",
"expect.js": "0.3.1", "expect.js": "0.3.1",
"front-matter": "^3.0.2", "front-matter": "^3.0.2",
"fs-extra": "^8.0.0", "fs-extra": "^8.0.0",
"gatsby": "^2.4.3",
"gatsby-plugin-emotion": "^4.0.6",
"gatsby-plugin-typography": "^2.2.13",
"glob": "^7.1.4", "glob": "^7.1.4",
"globby": "^9.2.0", "globby": "^9.2.0",
"handlebars": "4.1.2", "handlebars": "4.1.2",
@@ -69,7 +78,8 @@
"istanbul-instrumenter-loader": "^3.0.1", "istanbul-instrumenter-loader": "^3.0.1",
"jquery": "3.4.1", "jquery": "3.4.1",
"jsdoc": "3.6.2", "jsdoc": "3.6.2",
"jsdoc-plugin-typescript": "^2.0.0", "jsdoc-json": "^2.0.2",
"jsdoc-plugin-typescript": "^2.0.1",
"karma": "^4.1.0", "karma": "^4.1.0",
"karma-chrome-launcher": "2.2.0", "karma-chrome-launcher": "2.2.0",
"karma-coverage": "^1.1.2", "karma-coverage": "^1.1.2",
@@ -81,16 +91,29 @@
"loglevelnext": "^3.0.1", "loglevelnext": "^3.0.1",
"marked": "0.6.2", "marked": "0.6.2",
"mocha": "6.1.4", "mocha": "6.1.4",
"ol-mapbox-style": "^5.0.0-beta.1", "ol-mapbox-style": "^5.0.0-beta.2",
"pixelmatch": "^4.0.2", "pixelmatch": "^4.0.2",
"pngjs": "^3.4.0", "pngjs": "^3.4.0",
"proj4": "2.5.0", "proj4": "2.5.0",
"prop-types": "^15.7.2",
"puppeteer": "~1.16.0", "puppeteer": "~1.16.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-markdown": "^4.0.8",
"react-syntax-highlighter": "^10.2.1",
"react-typography": "^0.16.19",
"rollup": "^1.12.0",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-node-resolve": "^5.0.0",
"rollup-plugin-terser": "^4.0.4",
"serve-static": "^1.14.0", "serve-static": "^1.14.0",
"shx": "^0.3.2", "shx": "^0.3.2",
"sinon": "^7.3.2", "sinon": "^7.3.2",
"terser-webpack-plugin": "^1.2.3", "terser-webpack-plugin": "^1.2.3",
"typescript": "^3.4.5", "typescript": "^3.4.5",
"typography": "^0.16.19",
"typography-theme-alton": "^0.16.19",
"url-polyfill": "^1.1.5", "url-polyfill": "^1.1.5",
"walk": "^2.3.9", "walk": "^2.3.9",
"webpack": "4.31.0", "webpack": "4.31.0",

View File

@@ -42,7 +42,8 @@ const layer = new VectorLayer({
const view = new View({ const view = new View({
center: [-9.5, 78], center: [-9.5, 78],
zoom: 2, zoom: 2,
projection: 'EPSG:4326' projection: 'EPSG:4326',
multiWorld: true
}); });
new Map({ new Map({
pixelRatio: 1, pixelRatio: 1,

3
site/.eslintrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "tschaub/react"
}

35
site/components/Class.jsx Normal file
View File

@@ -0,0 +1,35 @@
import {object} from 'prop-types';
import React from 'react';
import Markdown from 'react-markdown';
import Code from './Code';
import Parameter from './Parameter';
function Class({cls, module, helper}) {
const exportedName = module.getExportedName(cls.name);
let importCode;
if (exportedName === 'default') {
importCode = `import ${cls.name} from '${module.id}';`;
} else {
importCode = `import {${exportedName}} from '${module.id}';`;
}
return (
<div>
<h3>{cls.name}</h3>
<Code value={importCode} />
<Markdown source={cls.doc.classdesc} renderers={{code: Code}} />
<h6>Parameters</h6>
<ul>
{cls.doc.params && cls.doc.params.map(param => <Parameter param={param} module={module} helper={helper} />)}
</ul>
</div>
);
}
Class.propTypes = {
cls: object.isRequired,
module: object.isRequired,
helper: object.isRequired
};
export default Class;

26
site/components/Code.jsx Normal file
View File

@@ -0,0 +1,26 @@
import React, {PureComponent} from 'react';
import {string} from 'prop-types';
import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter';
import {coy} from 'react-syntax-highlighter/dist/styles/prism';
class Code extends PureComponent {
render() {
let language = this.props.language;
if (!language) {
language = 'js';
}
return (
<SyntaxHighlighter language={language} style={coy}>
{this.props.value}
</SyntaxHighlighter>
);
}
}
Code.propTypes = {
value: string.isRequired,
language: string
};
export default Code;

35
site/components/Func.jsx Normal file
View File

@@ -0,0 +1,35 @@
import {object} from 'prop-types';
import React from 'react';
import Markdown from 'react-markdown';
import Code from './Code';
import Parameter from './Parameter';
function Func({func, module, helper}) {
const exportedName = module.getExportedName(func.name);
let importCode;
if (exportedName === 'default') {
importCode = `import ${func.name} from '${module.id}';`;
} else {
importCode = `import {${exportedName}} from '${module.id}';`;
}
return (
<div>
<h3>{func.name}</h3>
<Code value={importCode} />
<Markdown source={func.doc.description} renderers={{code: Code}} />
<h6>Parameters</h6>
<ul>
{func.doc.params && func.doc.params.map(param => <Parameter param={param} module={module} helper={helper} />)}
</ul>
</div>
);
}
Func.propTypes = {
func: object.isRequired,
module: object.isRequired,
helper: object.isRequired
};
export default Func;

View File

@@ -0,0 +1,26 @@
import {object} from 'prop-types';
import React from 'react';
import Class from './Class';
import Func from './Func';
function Module({module, helper}) {
return (
<div>
<hr />
<h2>{module.id}</h2>
{module.classes.map(cls => (
<Class key={cls.name} cls={cls} module={module} helper={helper} />
))}
{module.functions.map(func => (
<Func key={func.name} func={func} module={module} helper={helper} />
))}
</div>
);
}
Module.propTypes = {
module: object.isRequired,
helper: object.isRequired
};
export default Module;

View File

@@ -0,0 +1,20 @@
import {object} from 'prop-types';
import React from 'react';
import Type from './Type';
function Parameter({param, module, helper}) {
return (
<li>
<code>{param.name}</code> - {param.description} {param.optional && <span>(optional)</span>}<br/>
{param.type.names.map(longName => <Type longName={longName} module={module} helper={helper} />)}
</li>
);
}
Parameter.propTypes = {
param: object.isRequired,
module: object.isRequired,
helper: object.isRequired
};
export default Parameter;

27
site/components/Type.jsx Normal file
View File

@@ -0,0 +1,27 @@
import {object} from 'prop-types';
import React from 'react';
import Parameter from './Parameter';
function Type({longName, module, helper}) {
const type = helper.getTypeDef(longName);
if (!type) {
return <code>{longName}</code>;
}
return (
<div>
<code>{type.doc.type.names}</code>
<ul>
{type.doc.properties && type.doc.properties.map(prop => <Parameter param={prop} module={module} helper={helper} />)}
</ul>
</div>
);
}
Type.propTypes = {
longName: object.isRequired,
module: object.isRequired,
helper: object.isRequired
};
export default Type;

View File

@@ -0,0 +1,8 @@
import styled from '@emotion/styled';
import {baseSpacingPx} from '../util/typography';
export const Page = styled.div({
display: 'flex',
flexDirection: 'column',
margin: 2 * baseSpacingPx
});

26
site/pages/API.js Normal file
View File

@@ -0,0 +1,26 @@
import {object} from 'prop-types';
import React from 'react';
import {Page} from '../components/layout';
import Module from '../components/Module';
import {getHelper} from '../util/api';
function API({pageContext: {docs}}) {
const helper = getHelper(docs);
return (
<Page>
<h1>API</h1>
{helper.modules
.filter(module => module.visible)
.map(module => (
<Module key={module.id} module={module} helper={helper} />
))}
</Page>
);
}
API.propTypes = {
pageContext: object.isRequired
};
export default API;

33
site/pages/Info.js Normal file
View File

@@ -0,0 +1,33 @@
import {object} from 'prop-types';
import React from 'react';
import {Page} from '../components/layout';
function Info({pageContext: {docs}}) {
return (
<Page>
<h1>API</h1>
<table>
<tbody>
<tr>
<th>kind</th>
<th>longname</th>
<th>memberof</th>
</tr>
{docs.map(doc => (
<tr key={doc.longname}>
<td>{doc.kind}</td>
<td>{doc.longname}</td>
<td>{doc.memberof}</td>
</tr>
))}
</tbody>
</table>
</Page>
);
}
Info.propTypes = {
pageContext: object.isRequired
};
export default Info;

205
site/util/api.js Normal file
View File

@@ -0,0 +1,205 @@
class FunctionDoc {
constructor(doc) {
this.name = doc.name;
this.doc = doc;
}
}
class TypedefDoc {
constructor(doc) {
this.name = doc.name;
this.doc = doc;
}
}
class ClassDoc {
constructor(name) {
this.name = name;
}
processDoc(doc) {
if (doc.kind === 'class') {
this.doc = doc;
}
}
}
class ModuleDoc {
constructor(id) {
this.id = id;
this.classLookup = {};
this.classes = [];
this.functionLookup = {};
this.functions = [];
}
processDoc(doc) {
if (doc.kind === 'module') {
this.doc = doc;
//console.log('processing module: ' + doc.longname)
return;
}
if (doc.kind === 'class') {
const name = nameFromLongname(doc.longname);
if (!(name in this.classLookup)) {
const cls = new ClassDoc(name);
this.classLookup[name] = cls;
this.classes.push(cls);
}
this.classLookup[name].processDoc(doc);
return;
}
if (doc.kind === 'function') {
if (nameFromLongname(doc.memberof)) {
// belongs to a class or other
return;
}
if (doc.name in this.functionLookup) {
throw new Error(`Duplicate function ${doc.name} in ${this.id}`);
}
const func = new FunctionDoc(doc);
this.functionLookup[doc.name] = func;
this.functions.push(func);
return;
}
}
finalize() {
this.classes.sort(byName);
this.functions.sort(byName);
this.visible = this.classes.length > 0 || this.functions.length > 0;
}
getExportedName(localName) {
if (!this.doc || !this.doc.exportMap) {
throw new Error(`Expected to find export map in module doc: ${this.id}`);
}
if (!(localName in this.doc.exportMap)) {
throw new Error(
`No local name "${localName}" in export map for module: ${this.id}`
);
}
return this.doc.exportMap[localName];
}
}
const longnameRE = /^module:(?<module>.*?)([~\.](?<name>\w+)(#(?<member>\w+))?(:(?<type>\w+))?)?$/;
function moduleIDFromLongname(longname) {
const match = longname.match(longnameRE);
if (!match) {
throw new Error(`could not match module id in longname: ${longname}`);
}
return match.groups.module;
}
export function nameFromLongname(longname) {
const match = longname.match(longnameRE);
if (!match) {
throw new Error(`could not match name in longname: ${longname}`);
}
return match.groups.name;
}
function memberFromLongname(longname) {
const match = longname.match(longnameRE);
if (!match) {
throw new Error(`could not match member in longname: ${longname}`);
}
return match.groups.member;
}
function byName(a, b) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
}
function byModuleId(a, b) {
const aParts = a.id.split('/');
const bParts = b.id.split('/');
const len = Math.max(aParts.length, bParts.length);
for (let i = 0; i < len; ++i) {
if (aParts[i] && bParts[i]) {
if (aParts[i] < bParts[i]) {
return -1;
}
if (aParts[i] > bParts[i]) {
return 1;
}
} else if (!aParts[i]) {
return -1;
} else {
return 1;
}
}
return 0;
}
class DocHelper {
constructor(docs) {
this.moduleLookup = {};
this.modules = [];
this.typedefLookup = {};
docs.forEach(doc => {
// typedef are indexed by long name
if (doc.kind === 'typedef') {
if (doc.name in this.typedefLookup) {
throw new Error(`Duplicate type definition ${doc.name} in ${this.id}`);
}
const type = new TypedefDoc(doc);
this.typedefLookup[doc.longname] = type;
return;
}
const moduleID = moduleIDFromLongname(doc.longname);
if (!(moduleID in this.moduleLookup)) {
const module = new ModuleDoc(moduleID);
this.moduleLookup[moduleID] = module;
this.modules.push(module);
}
const module = this.moduleLookup[moduleID];
module.processDoc(doc);
});
this.modules.sort(byModuleId);
this.modules.forEach(module => module.finalize());
}
getTypeDef(longName) {
this.typedefLookup[longName] && console.log(this.typedefLookup[longName]);
return this.typedefLookup[longName];
}
}
let cachedDocs;
let cachedHelper;
export function getHelper(docs) {
if (docs !== cachedDocs) {
if (cachedDocs) {
console.warn('creating new doc helper'); // eslint-disable-line
}
cachedHelper = new DocHelper(docs);
cachedDocs = docs;
}
return cachedHelper;
}

9
site/util/typography.js Normal file
View File

@@ -0,0 +1,9 @@
import Typography from 'typography';
import theme from 'typography-theme-alton';
const typography = new Typography(theme);
export const baseSpacingPx = parseInt(theme.baseFontSize, 10);
export {theme};
export default typography;

View File

@@ -6,7 +6,6 @@ import {listen} from './events.js';
import Event from './events/Event.js'; import Event from './events/Event.js';
import EventType from './events/EventType.js'; import EventType from './events/EventType.js';
import {circular as circularPolygon} from './geom/Polygon.js'; import {circular as circularPolygon} from './geom/Polygon.js';
import {GEOLOCATION} from './has.js';
import {toRadians} from './math.js'; import {toRadians} from './math.js';
import {get as getProjection, getTransformFromProjections, identityTransform} from './proj.js'; import {get as getProjection, getTransformFromProjections, identityTransform} from './proj.js';
@@ -83,7 +82,7 @@ class GeolocationError extends Event {
* window.console.log(geolocation.getPosition()); * window.console.log(geolocation.getPosition());
* }); * });
* *
* @fires error * @fires module:ol/events/Event~Event#event:error
* @api * @api
*/ */
class Geolocation extends BaseObject { class Geolocation extends BaseObject {
@@ -160,7 +159,7 @@ class Geolocation extends BaseObject {
* @private * @private
*/ */
handleTrackingChanged_() { handleTrackingChanged_() {
if (GEOLOCATION) { if ('geolocation' in navigator) {
const tracking = this.getTracking(); const tracking = this.getTracking();
if (tracking && this.watchId_ === undefined) { if (tracking && this.watchId_ === undefined) {
this.watchId_ = navigator.geolocation.watchPosition( this.watchId_ = navigator.geolocation.watchPosition(
@@ -204,12 +203,6 @@ class Geolocation extends BaseObject {
this.changed(); this.changed();
} }
/**
* Triggered when the Geolocation returns an error.
* @event error
* @api
*/
/** /**
* @private * @private
* @param {PositionError} error error object. * @param {PositionError} error error object.

View File

@@ -58,9 +58,9 @@ class ImageWrapper extends ImageBase {
/** /**
* @private * @private
* @type {Array<import("./events.js").EventsKey>} * @type {function():void}
*/ */
this.imageListenerKeys_ = null; this.unlisten_ = null;
/** /**
* @protected * @protected
@@ -120,13 +120,12 @@ class ImageWrapper extends ImageBase {
if (this.state == ImageState.IDLE || this.state == ImageState.ERROR) { if (this.state == ImageState.IDLE || this.state == ImageState.ERROR) {
this.state = ImageState.LOADING; this.state = ImageState.LOADING;
this.changed(); this.changed();
this.imageListenerKeys_ = [
listenOnce(this.image_, EventType.ERROR,
this.handleImageError_, this),
listenOnce(this.image_, EventType.LOAD,
this.handleImageLoad_, this)
];
this.imageLoadFunction_(this, this.src_); this.imageLoadFunction_(this, this.src_);
this.unlisten_ = listenImage(
this.image_,
this.handleImageLoad_.bind(this),
this.handleImageError_.bind(this)
);
} }
} }
@@ -143,10 +142,47 @@ class ImageWrapper extends ImageBase {
* @private * @private
*/ */
unlistenImage_() { unlistenImage_() {
this.imageListenerKeys_.forEach(unlistenByKey); if (this.unlisten_) {
this.imageListenerKeys_ = null; this.unlisten_();
this.unlisten_ = null;
}
} }
} }
/**
* @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image element.
* @param {function():any} loadHandler Load callback function.
* @param {function():any} errorHandler Error callback function.
* @return {function():void} Callback to stop listening.
*/
export function listenImage(image, loadHandler, errorHandler) {
const img = /** @type {HTMLImageElement} */ (image);
if (img.decode) {
const promise = img.decode();
let listening = true;
const unlisten = function() {
listening = false;
};
promise.then(function() {
if (listening) {
loadHandler();
}
}).catch(function(error) {
if (listening) {
errorHandler();
}
});
return unlisten;
}
const listenerKeys = [
listenOnce(img, EventType.LOAD, loadHandler),
listenOnce(img, EventType.ERROR, errorHandler)
];
return function unlisten() {
listenerKeys.forEach(unlistenByKey);
};
}
export default ImageWrapper; export default ImageWrapper;

View File

@@ -4,8 +4,7 @@
import Tile from './Tile.js'; import Tile from './Tile.js';
import TileState from './TileState.js'; import TileState from './TileState.js';
import {createCanvasContext2D} from './dom.js'; import {createCanvasContext2D} from './dom.js';
import {listenOnce, unlistenByKey} from './events.js'; import {listenImage} from './Image.js';
import EventType from './events/EventType.js';
class ImageTile extends Tile { class ImageTile extends Tile {
@@ -47,9 +46,9 @@ class ImageTile extends Tile {
/** /**
* @private * @private
* @type {Array<import("./events.js").EventsKey>} * @type {function():void}
*/ */
this.imageListenerKeys_ = null; this.unlisten_ = null;
/** /**
* @private * @private
@@ -134,13 +133,12 @@ class ImageTile extends Tile {
if (this.state == TileState.IDLE) { if (this.state == TileState.IDLE) {
this.state = TileState.LOADING; this.state = TileState.LOADING;
this.changed(); this.changed();
this.imageListenerKeys_ = [
listenOnce(this.image_, EventType.ERROR,
this.handleImageError_, this),
listenOnce(this.image_, EventType.LOAD,
this.handleImageLoad_, this)
];
this.tileLoadFunction_(this, this.src_); this.tileLoadFunction_(this, this.src_);
this.unlisten_ = listenImage(
this.image_,
this.handleImageLoad_.bind(this),
this.handleImageError_.bind(this)
);
} }
} }
@@ -150,8 +148,10 @@ class ImageTile extends Tile {
* @private * @private
*/ */
unlistenImage_() { unlistenImage_() {
this.imageListenerKeys_.forEach(unlistenByKey); if (this.unlisten_) {
this.imageListenerKeys_ = null; this.unlisten_();
this.unlisten_ = null;
}
} }
} }

View File

@@ -2,7 +2,7 @@
* @module ol/Map * @module ol/Map
*/ */
import PluggableMap from './PluggableMap.js'; import PluggableMap from './PluggableMap.js';
import {defaults as defaultControls} from './control/util.js'; import {defaults as defaultControls} from './control.js';
import {defaults as defaultInteractions} from './interaction.js'; import {defaults as defaultInteractions} from './interaction.js';
import {assign} from './obj.js'; import {assign} from './obj.js';
import CompositeMapRenderer from './renderer/Composite.js'; import CompositeMapRenderer from './renderer/Composite.js';

View File

@@ -293,6 +293,11 @@ class PluggableMap extends BaseObject {
*/ */
this.interactions = optionsInternal.interactions || new Collection(); this.interactions = optionsInternal.interactions || new Collection();
/**
* @type {import("./events.js").EventsKey}
*/
this.labelCacheListenerKey_;
/** /**
* @type {Collection<import("./Overlay.js").default>} * @type {Collection<import("./Overlay.js").default>}
* @private * @private
@@ -498,6 +503,24 @@ class PluggableMap extends BaseObject {
overlay.setMap(this); overlay.setMap(this);
} }
/**
* Attach a label cache for listening to font changes.
* @param {import("./events/Target.js").default} labelCache Label cache.
*/
attachLabelCache(labelCache) {
this.detachLabelCache();
this.labelCacheListenerKey_ = listen(labelCache, EventType.CLEAR, this.redrawText.bind(this));
}
/**
* Detach the label cache, i.e. no longer listen to font changes.
*/
detachLabelCache() {
if (this.labelCacheListenerKey_) {
unlistenByKey(this.labelCacheListenerKey_);
}
}
/** /**
* *
* @inheritDoc * @inheritDoc
@@ -515,6 +538,7 @@ class PluggableMap extends BaseObject {
cancelAnimationFrame(this.animationDelayKey_); cancelAnimationFrame(this.animationDelayKey_);
this.animationDelayKey_ = undefined; this.animationDelayKey_ = undefined;
} }
this.detachLabelCache();
this.setTarget(null); this.setTarget(null);
super.disposeInternal(); super.disposeInternal();
} }
@@ -763,6 +787,21 @@ class PluggableMap extends BaseObject {
return layers; return layers;
} }
/**
* @return {boolean} Layers have sources that are still loading.
*/
getLoading() {
const layerStatesArray = this.getLayerGroup().getLayerStatesArray();
for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) {
const layer = layerStatesArray[i].layer;
const source = /** @type {import("./layer/Layer.js").default} */ (layer).getSource();
if (source && source.loading) {
return true;
}
}
return false;
}
/** /**
* Get the pixel for a coordinate. This takes a coordinate in the map view * Get the pixel for a coordinate. This takes a coordinate in the map view
* projection and returns the corresponding pixel. * projection and returns the corresponding pixel.
@@ -950,7 +989,7 @@ class PluggableMap extends BaseObject {
} }
if (frameState && this.hasListener(RenderEventType.RENDERCOMPLETE) && !frameState.animate && if (frameState && this.hasListener(RenderEventType.RENDERCOMPLETE) && !frameState.animate &&
!this.tileQueue_.getTilesLoading() && !getLoading(this.getLayers().getArray())) { !this.tileQueue_.getTilesLoading() && !this.getLoading()) {
this.renderer_.dispatchRenderEvent(RenderEventType.RENDERCOMPLETE, frameState); this.renderer_.dispatchRenderEvent(RenderEventType.RENDERCOMPLETE, frameState);
} }
@@ -994,7 +1033,6 @@ class PluggableMap extends BaseObject {
} }
if (!targetElement) { if (!targetElement) {
this.renderer_.removeLayerRenderers();
removeNode(this.viewport_); removeNode(this.viewport_);
if (this.handleResize_ !== undefined) { if (this.handleResize_ !== undefined) {
removeEventListener(EventType.RESIZE, this.handleResize_, false); removeEventListener(EventType.RESIZE, this.handleResize_, false);
@@ -1102,6 +1140,19 @@ class PluggableMap extends BaseObject {
this.animationDelay_(); this.animationDelay_();
} }
/**
* Redraws all text after new fonts have loaded
*/
redrawText() {
const layerStates = this.getLayerGroup().getLayerStatesArray();
for (let i = 0, ii = layerStates.length; i < ii; ++i) {
const layer = layerStates[i].layer;
if (layer.hasRenderer()) {
layer.getRenderer().handleFontsChanged();
}
}
}
/** /**
* Request a map rendering (at the next animation frame). * Request a map rendering (at the next animation frame).
* @api * @api
@@ -1398,23 +1449,3 @@ function createOptionsInternal(options) {
} }
export default PluggableMap; export default PluggableMap;
/**
* @param {Array<import("./layer/Base.js").default>} layers Layers.
* @return {boolean} Layers have sources that are still loading.
*/
function getLoading(layers) {
for (let i = 0, ii = layers.length; i < ii; ++i) {
const layer = layers[i];
if (typeof /** @type {?} */ (layer).getLayers === 'function') {
return getLoading(/** @type {LayerGroup} */ (layer).getLayers().getArray());
} else {
const source = /** @type {import("./layer/Layer.js").default} */ (
layer).getSource();
if (source && source.loading) {
return true;
}
}
}
return false;
}

View File

@@ -1409,11 +1409,19 @@ function animationCallback(callback, returnValue) {
*/ */
export function createCenterConstraint(options) { export function createCenterConstraint(options) {
if (options.extent !== undefined) { if (options.extent !== undefined) {
return createExtent(options.extent, options.constrainOnlyCenter, const smooth = options.smoothExtentConstraint !== undefined ? options.smoothExtentConstraint : true;
options.smoothExtentConstraint !== undefined ? options.smoothExtentConstraint : true); return createExtent(options.extent, options.constrainOnlyCenter, smooth);
} else {
return centerNone;
} }
const projection = createProjection(options.projection, 'EPSG:3857');
if (options.multiWorld !== true && projection.isGlobal()) {
const extent = projection.getExtent().slice();
extent[0] = -Infinity;
extent[2] = Infinity;
return createExtent(extent, false, false);
}
return centerNone;
} }

View File

@@ -1,6 +1,10 @@
/** /**
* @module ol/control * @module ol/control
*/ */
import Collection from './Collection.js';
import Attribution from './control/Attribution.js';
import Rotate from './control/Rotate.js';
import Zoom from './control/Zoom.js';
export {default as Attribution} from './control/Attribution.js'; export {default as Attribution} from './control/Attribution.js';
export {default as Control} from './control/Control.js'; export {default as Control} from './control/Control.js';
@@ -12,4 +16,59 @@ export {default as ScaleLine} from './control/ScaleLine.js';
export {default as Zoom} from './control/Zoom.js'; export {default as Zoom} from './control/Zoom.js';
export {default as ZoomSlider} from './control/ZoomSlider.js'; export {default as ZoomSlider} from './control/ZoomSlider.js';
export {default as ZoomToExtent} from './control/ZoomToExtent.js'; export {default as ZoomToExtent} from './control/ZoomToExtent.js';
export {defaults} from './control/util.js';
/**
* @typedef {Object} DefaultsOptions
* @property {boolean} [attribution=true] Include
* {@link module:ol/control/Attribution~Attribution}.
* @property {import("./control/Attribution.js").Options} [attributionOptions]
* Options for {@link module:ol/control/Attribution~Attribution}.
* @property {boolean} [rotate=true] Include
* {@link module:ol/control/Rotate~Rotate}.
* @property {import("./control/Rotate.js").Options} [rotateOptions] Options
* for {@link module:ol/control/Rotate~Rotate}.
* @property {boolean} [zoom] Include {@link module:ol/control/Zoom~Zoom}.
* @property {import("./control/Zoom.js").Options} [zoomOptions] Options for
* {@link module:ol/control/Zoom~Zoom}.
* @api
*/
/**
* Set of controls included in maps by default. Unless configured otherwise,
* this returns a collection containing an instance of each of the following
* controls:
* * {@link module:ol/control/Zoom~Zoom}
* * {@link module:ol/control/Rotate~Rotate}
* * {@link module:ol/control/Attribution~Attribution}
*
* @param {DefaultsOptions=} opt_options
* Defaults options.
* @return {Collection<import("./control/Control.js").default>}
* Controls.
* @api
*/
export function defaults(opt_options) {
const options = opt_options ? opt_options : {};
const controls = new Collection();
const zoomControl = options.zoom !== undefined ? options.zoom : true;
if (zoomControl) {
controls.push(new Zoom(options.zoomOptions));
}
const rotateControl = options.rotate !== undefined ? options.rotate : true;
if (rotateControl) {
controls.push(new Rotate(options.rotateOptions));
}
const attributionControl = options.attribution !== undefined ?
options.attribution : true;
if (attributionControl) {
controls.push(new Attribution(options.attributionOptions));
}
return controls;
}

View File

@@ -1,8 +1,8 @@
/** /**
* @module ol/control/OverviewMap * @module ol/control/OverviewMap
*/ */
import Collection from '../Collection.js'; import PluggableMap from '../PluggableMap.js';
import Map from '../Map.js'; import CompositeMapRenderer from '../renderer/Composite.js';
import MapEventType from '../MapEventType.js'; import MapEventType from '../MapEventType.js';
import MapProperty from '../MapProperty.js'; import MapProperty from '../MapProperty.js';
import {getChangeEventType} from '../Object.js'; import {getChangeEventType} from '../Object.js';
@@ -35,6 +35,13 @@ const MAX_RATIO = 0.75;
const MIN_RATIO = 0.1; const MIN_RATIO = 0.1;
class ControlledMap extends PluggableMap {
createRenderer() {
return new CompositeMapRenderer(this);
}
}
/** /**
* @typedef {Object} Options * @typedef {Object} Options
* @property {string} [className='ol-overviewmap'] CSS class name. * @property {string} [className='ol-overviewmap'] CSS class name.
@@ -143,12 +150,10 @@ class OverviewMap extends Control {
this.ovmapDiv_.className = 'ol-overviewmap-map'; this.ovmapDiv_.className = 'ol-overviewmap-map';
/** /**
* @type {import("../Map.js").default} * @type {ControlledMap}
* @private * @private
*/ */
this.ovmap_ = new Map({ this.ovmap_ = new ControlledMap({
controls: new Collection(),
interactions: new Collection(),
view: options.view view: options.view
}); });
const ovmap = this.ovmap_; const ovmap = this.ovmap_;

View File

@@ -1,65 +0,0 @@
/**
* @module ol/control/util
*/
import Collection from '../Collection.js';
import Attribution from './Attribution.js';
import Rotate from './Rotate.js';
import Zoom from './Zoom.js';
/**
* @typedef {Object} DefaultsOptions
* @property {boolean} [attribution=true] Include
* {@link module:ol/control/Attribution~Attribution}.
* @property {import("./Attribution.js").Options} [attributionOptions]
* Options for {@link module:ol/control/Attribution~Attribution}.
* @property {boolean} [rotate=true] Include
* {@link module:ol/control/Rotate~Rotate}.
* @property {import("./Rotate.js").Options} [rotateOptions] Options
* for {@link module:ol/control/Rotate~Rotate}.
* @property {boolean} [zoom] Include {@link module:ol/control/Zoom~Zoom}.
* @property {import("./Zoom.js").Options} [zoomOptions] Options for
* {@link module:ol/control/Zoom~Zoom}.
* @api
*/
/**
* Set of controls included in maps by default. Unless configured otherwise,
* this returns a collection containing an instance of each of the following
* controls:
* * {@link module:ol/control/Zoom~Zoom}
* * {@link module:ol/control/Rotate~Rotate}
* * {@link module:ol/control/Attribution~Attribution}
*
* @param {DefaultsOptions=} opt_options
* Defaults options.
* @return {Collection<import("./Control.js").default>}
* Controls.
* @function module:ol/control.defaults
* @api
*/
export function defaults(opt_options) {
const options = opt_options ? opt_options : {};
const controls = new Collection();
const zoomControl = options.zoom !== undefined ? options.zoom : true;
if (zoomControl) {
controls.push(new Zoom(options.zoomOptions));
}
const rotateControl = options.rotate !== undefined ? options.rotate : true;
if (rotateControl) {
controls.push(new Rotate(options.rotateOptions));
}
const attributionControl = options.attribution !== undefined ?
options.attribution : true;
if (attributionControl) {
controls.push(new Attribution(options.attributionOptions));
}
return controls;
}

View File

@@ -14,6 +14,13 @@ export default {
*/ */
CHANGE: 'change', CHANGE: 'change',
/**
* Generic error event. Triggered when an error occurs.
* @event module:ol/events/Event~Event#error
* @api
*/
ERROR: 'error',
CLEAR: 'clear', CLEAR: 'clear',
CONTEXTMENU: 'contextmenu', CONTEXTMENU: 'contextmenu',
CLICK: 'click', CLICK: 'click',
@@ -21,7 +28,6 @@ export default {
DRAGENTER: 'dragenter', DRAGENTER: 'dragenter',
DRAGOVER: 'dragover', DRAGOVER: 'dragover',
DROP: 'drop', DROP: 'drop',
ERROR: 'error',
KEYDOWN: 'keydown', KEYDOWN: 'keydown',
KEYPRESS: 'keypress', KEYPRESS: 'keypress',
LOAD: 'load', LOAD: 'load',

View File

@@ -2684,13 +2684,6 @@ function writePlacemark(node, feature, objectStack) {
return !filter[v]; return !filter[v];
}); });
if (keys.length > 0) {
const sequence = makeSequence(properties, keys);
const namesAndValues = {names: keys, values: sequence};
pushSerializeAndPop(context, PLACEMARK_SERIALIZERS,
EXTENDEDDATA_NODE_FACTORY, [namesAndValues], objectStack);
}
const styleFunction = feature.getStyleFunction(); const styleFunction = feature.getStyleFunction();
if (styleFunction) { if (styleFunction) {
// FIXME the styles returned by the style function are supposed to be // FIXME the styles returned by the style function are supposed to be
@@ -2713,6 +2706,13 @@ function writePlacemark(node, feature, objectStack) {
pushSerializeAndPop(context, PLACEMARK_SERIALIZERS, pushSerializeAndPop(context, PLACEMARK_SERIALIZERS,
OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
if (keys.length > 0) {
const sequence = makeSequence(properties, keys);
const namesAndValues = {names: keys, values: sequence};
pushSerializeAndPop(context, PLACEMARK_SERIALIZERS,
EXTENDEDDATA_NODE_FACTORY, [namesAndValues], objectStack);
}
// serialize geometry // serialize geometry
const options = /** @type {import("./Feature.js").WriteOptions} */ (objectStack[0]); const options = /** @type {import("./Feature.js").WriteOptions} */ (objectStack[0]);
let geometry = feature.getGeometry(); let geometry = feature.getGeometry();

View File

@@ -16,8 +16,8 @@ import {lerp} from '../../math.js';
* @param {function(string, string, Object<string, number>):number} measureAndCacheTextWidth Measure and cache text width. * @param {function(string, string, Object<string, number>):number} measureAndCacheTextWidth Measure and cache text width.
* @param {string} font The font. * @param {string} font The font.
* @param {Object<string, number>} cache A cache of measured widths. * @param {Object<string, number>} cache A cache of measured widths.
* @return {Array<Array<*>>} The result array of null if `maxAngle` was * @return {Array<Array<*>>} The result array (or null if `maxAngle` was
* exceeded. Entries of the array are x, y, anchorX, angle, chunk. * exceeded). Entries of the array are x, y, anchorX, angle, chunk.
*/ */
export function drawTextOnPath( export function drawTextOnPath(
flatCoordinates, offset, end, stride, text, startM, maxAngle, scale, measureAndCacheTextWidth, font, cache) { flatCoordinates, offset, end, stride, text, startM, maxAngle, scale, measureAndCacheTextWidth, font, cache) {
@@ -35,16 +35,13 @@ export function drawTextOnPath(
let y2 = flatCoordinates[offset + 1]; let y2 = flatCoordinates[offset + 1];
let segmentM = 0; let segmentM = 0;
let segmentLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); let segmentLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
let angleChanged = false;
let chunk = ''; let index, previousAngle;
let chunkLength = 0;
let data, index, previousAngle;
for (let i = 0; i < numChars; ++i) { for (let i = 0; i < numChars; ++i) {
index = reverse ? numChars - i - 1 : i; index = reverse ? numChars - i - 1 : i;
const char = text.charAt(index); const char = text[index];
chunk = reverse ? char + chunk : chunk + char; const charLength = scale * measureAndCacheTextWidth(font, char, cache);
const charLength = scale * measureAndCacheTextWidth(font, chunk, cache) - chunkLength;
chunkLength += charLength;
const charM = startM + charLength / 2; const charM = startM + charLength / 2;
while (offset < end - stride && segmentM + segmentLength < charM) { while (offset < end - stride && segmentM + segmentLength < charM) {
x1 = x2; x1 = x2;
@@ -62,33 +59,18 @@ export function drawTextOnPath(
} }
if (previousAngle !== undefined) { if (previousAngle !== undefined) {
let delta = angle - previousAngle; let delta = angle - previousAngle;
angleChanged = angleChanged || delta !== 0;
delta += (delta > Math.PI) ? -2 * Math.PI : (delta < -Math.PI) ? 2 * Math.PI : 0; delta += (delta > Math.PI) ? -2 * Math.PI : (delta < -Math.PI) ? 2 * Math.PI : 0;
if (Math.abs(delta) > maxAngle) { if (Math.abs(delta) > maxAngle) {
return null; return null;
} }
} }
previousAngle = angle;
const interpolate = segmentPos / segmentLength; const interpolate = segmentPos / segmentLength;
const x = lerp(x1, x2, interpolate); const x = lerp(x1, x2, interpolate);
const y = lerp(y1, y2, interpolate); const y = lerp(y1, y2, interpolate);
if (previousAngle == angle) { result[index] = [x, y, charLength / 2, angle, char];
if (reverse) {
data[0] = x;
data[1] = y;
data[2] = charLength / 2;
}
data[4] = chunk;
} else {
chunk = char;
chunkLength = charLength;
data = [x, y, charLength / 2, angle, chunk];
if (reverse) {
result.unshift(data);
} else {
result.push(data);
}
previousAngle = angle;
}
startM += charLength; startM += charLength;
} }
return result; return angleChanged ? result : [[result[0][0], result[0][1], result[0][2], result[0][3], text]];
} }

View File

@@ -40,15 +40,6 @@ export const MAC = ua.indexOf('macintosh') !== -1;
export const DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1; export const DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;
/**
* Is HTML5 geolocation supported in the current browser?
* @const
* @type {boolean}
* @api
*/
export const GEOLOCATION = 'geolocation' in navigator;
/** /**
* True if browser supports touch events. * True if browser supports touch events.
* @const * @const

View File

@@ -254,6 +254,13 @@ class Layer extends BaseLayer {
return this.renderer_; return this.renderer_;
} }
/**
* @return {boolean} The layer has a renderer.
*/
hasRenderer() {
return !!this.renderer_;
}
/** /**
* Create a renderer for this layer. * Create a renderer for this layer.
* @return {import("../renderer/Layer.js").default} A layer renderer. * @return {import("../renderer/Layer.js").default} A layer renderer.

View File

@@ -1,7 +1,6 @@
/** /**
* @module ol/math * @module ol/math
*/ */
import {assert} from './asserts.js';
/** /**
* Takes a number and clamps it to within the provided bounds. * Takes a number and clamps it to within the provided bounds.
@@ -43,16 +42,6 @@ export const cosh = (function() {
}()); }());
/**
* @param {number} x X.
* @return {number} The smallest power of two greater than or equal to x.
*/
export function roundUpToPowerOfTwo(x) {
assert(0 < x, 29); // `x` must be greater than `0`
return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2));
}
/** /**
* Returns the square of the closest distance between the point (x, y) and the * Returns the square of the closest distance between the point (x, y) and the
* line segment (x1, y1) to (x2, y2). * line segment (x1, y1) to (x2, y2).

View File

@@ -127,11 +127,6 @@
right: .5em; right: .5em;
top: .5em; top: .5em;
} }
@media print {
.ol-control {
display: none;
}
}
.ol-control button { .ol-control button {
display: block; display: block;

View File

@@ -21,7 +21,7 @@ import {
import {createCanvasContext2D} from '../../dom.js'; import {createCanvasContext2D} from '../../dom.js';
import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js'; import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
import Disposable from '../../Disposable.js'; import Disposable from '../../Disposable.js';
import rbush from 'rbush'; import RBush from 'rbush';
/** /**
@@ -193,9 +193,10 @@ class Executor extends Disposable {
const width = measureTextWidths(textState.font, lines, widths); const width = measureTextWidths(textState.font, lines, widths);
const lineHeight = measureTextHeight(textState.font); const lineHeight = measureTextHeight(textState.font);
const height = lineHeight * numLines; const height = lineHeight * numLines;
const renderWidth = (width + strokeWidth); const renderWidth = width + strokeWidth;
const context = createCanvasContext2D( const context = createCanvasContext2D(
Math.ceil(renderWidth * scale), // make canvas 2 pixels wider to account for italic text width measurement errors
Math.ceil((renderWidth + 2) * scale),
Math.ceil((height + strokeWidth) * scale)); Math.ceil((height + strokeWidth) * scale));
label = context.canvas; label = context.canvas;
labelCache.set(key, label); labelCache.set(key, label);
@@ -220,7 +221,7 @@ class Executor extends Disposable {
context.textBaseline = 'middle'; context.textBaseline = 'middle';
context.textAlign = 'center'; context.textAlign = 'center';
const leftRight = (0.5 - align); const leftRight = (0.5 - align);
const x = align * label.width / scale + leftRight * strokeWidth; const x = align * renderWidth + leftRight * strokeWidth;
let i; let i;
if (strokeKey) { if (strokeKey) {
for (i = 0; i < numLines; ++i) { for (i = 0; i < numLines; ++i) {
@@ -429,7 +430,7 @@ class Executor extends Disposable {
value: feature value: feature
}; };
if (!declutterTree) { if (!declutterTree) {
declutterTree = rbush(9, undefined); declutterTree = new RBush(9);
} }
if (!declutterTree.collides(box)) { if (!declutterTree.collides(box)) {
declutterTree.insert(box); declutterTree.insert(box);
@@ -471,7 +472,9 @@ class Executor extends Disposable {
const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline]; const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline];
const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0; const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;
const anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth; // Remove the 2 pixels we added in getTextImage() for the anchor
const width = label.width / pixelRatio - 2 * textState.scale;
const anchorX = align * width + 2 * (0.5 - align) * strokeWidth;
const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth; const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth;
return { return {

View File

@@ -82,7 +82,7 @@ class ExecutorGroup extends Disposable {
* @private * @private
* @type {CanvasRenderingContext2D} * @type {CanvasRenderingContext2D}
*/ */
this.hitDetectionContext_ = createCanvasContext2D(1, 1); this.hitDetectionContext_ = null;
/** /**
* @private * @private
@@ -137,8 +137,11 @@ class ExecutorGroup extends Disposable {
executors[key].disposeInternal(); executors[key].disposeInternal();
} }
} }
const canvas = this.hitDetectionContext_.canvas; if (this.hitDetectionContext_) {
canvas.width = canvas.height = 0; const canvas = this.hitDetectionContext_.canvas;
canvas.width = canvas.height = 0;
}
super.disposeInternal(); super.disposeInternal();
} }
@@ -187,6 +190,10 @@ class ExecutorGroup extends Disposable {
1 / resolution, -1 / resolution, 1 / resolution, -1 / resolution,
-rotation, -rotation,
-coordinate[0], -coordinate[1]); -coordinate[0], -coordinate[1]);
if (!this.hitDetectionContext_) {
this.hitDetectionContext_ = createCanvasContext2D(contextSize, contextSize);
}
const context = this.hitDetectionContext_; const context = this.hitDetectionContext_;
if (context.canvas.width !== contextSize || context.canvas.height !== contextSize) { if (context.canvas.width !== contextSize || context.canvas.height !== contextSize) {

View File

@@ -8,6 +8,7 @@ import RenderEventType from '../render/EventType.js';
import MapRenderer from './Map.js'; import MapRenderer from './Map.js';
import SourceState from '../source/State.js'; import SourceState from '../source/State.js';
import {replaceChildren} from '../dom.js'; import {replaceChildren} from '../dom.js';
import {labelCache} from '../render/canvas.js';
/** /**
@@ -22,6 +23,7 @@ class CompositeMapRenderer extends MapRenderer {
*/ */
constructor(map) { constructor(map) {
super(map); super(map);
map.attachLabelCache(labelCache);
/** /**
* @private * @private
@@ -111,7 +113,6 @@ class CompositeMapRenderer extends MapRenderer {
this.renderedVisible_ = true; this.renderedVisible_ = true;
} }
this.scheduleRemoveUnusedLayerRenderers(frameState);
this.scheduleExpireIconCache(frameState); this.scheduleExpireIconCache(frameState);
} }
@@ -128,11 +129,8 @@ class CompositeMapRenderer extends MapRenderer {
for (let i = numLayers - 1; i >= 0; --i) { for (let i = numLayers - 1; i >= 0; --i) {
const layerState = layerStates[i]; const layerState = layerStates[i];
const layer = layerState.layer; const layer = layerState.layer;
if (visibleAtResolution(layerState, viewResolution) && layerFilter(layer)) { if (layer.hasRenderer() && visibleAtResolution(layerState, viewResolution) && layerFilter(layer)) {
const layerRenderer = this.getLayerRenderer(layer); const layerRenderer = layer.getRenderer();
if (!layerRenderer) {
continue;
}
const data = layerRenderer.getDataAtPixel(pixel, frameState, hitTolerance); const data = layerRenderer.getDataAtPixel(pixel, frameState, hitTolerance);
if (data) { if (data) {
const result = callback(layer, data); const result = callback(layer, data);

View File

@@ -115,6 +115,12 @@ class LayerRenderer extends Observable {
return this.layer_; return this.layer_;
} }
/**
* Perform action necessary to get the layer rendered after new fonts have loaded
* @abstract
*/
handleFontsChanged() {}
/** /**
* Handle changes in image state. * Handle changes in image state.
* @param {import("../events/Event.js").default} event Image change event. * @param {import("../events/Event.js").default} event Image change event.

View File

@@ -3,8 +3,6 @@
*/ */
import {abstract, getUid} from '../util.js'; import {abstract, getUid} from '../util.js';
import Disposable from '../Disposable.js'; import Disposable from '../Disposable.js';
import {listen, unlistenByKey} from '../events.js';
import EventType from '../events/EventType.js';
import {getWidth} from '../extent.js'; import {getWidth} from '../extent.js';
import {TRUE} from '../functions.js'; import {TRUE} from '../functions.js';
import {visibleAtResolution} from '../layer/Layer.js'; import {visibleAtResolution} from '../layer/Layer.js';
@@ -34,18 +32,6 @@ class MapRenderer extends Disposable {
*/ */
this.declutterTree_ = null; this.declutterTree_ = null;
/**
* @private
* @type {!Object<string, import("./Layer.js").default>}
*/
this.layerRenderers_ = {};
/**
* @private
* @type {Object<string, import("../events.js").EventsKey>}
*/
this.layerRendererListeners_ = {};
} }
/** /**
@@ -75,15 +61,6 @@ class MapRenderer extends Disposable {
makeInverse(pixelToCoordinateTransform, coordinateToPixelTransform); makeInverse(pixelToCoordinateTransform, coordinateToPixelTransform);
} }
/**
* Removes all layer renderers.
*/
removeLayerRenderers() {
for (const key in this.layerRenderers_) {
this.removeLayerRendererByKey_(key).dispose();
}
}
/** /**
* @param {import("../coordinate.js").Coordinate} coordinate Coordinate. * @param {import("../coordinate.js").Coordinate} coordinate Coordinate.
* @param {import("../PluggableMap.js").FrameState} frameState FrameState. * @param {import("../PluggableMap.js").FrameState} frameState FrameState.
@@ -149,8 +126,8 @@ class MapRenderer extends Disposable {
for (i = numLayers - 1; i >= 0; --i) { for (i = numLayers - 1; i >= 0; --i) {
const layerState = layerStates[i]; const layerState = layerStates[i];
const layer = /** @type {import("../layer/Layer.js").default} */ (layerState.layer); const layer = /** @type {import("../layer/Layer.js").default} */ (layerState.layer);
if (visibleAtResolution(layerState, viewResolution) && layerFilter.call(thisArg2, layer)) { if (layer.hasRenderer() && visibleAtResolution(layerState, viewResolution) && layerFilter.call(thisArg2, layer)) {
const layerRenderer = this.getLayerRenderer(layer); const layerRenderer = layer.getRenderer();
const source = layer.getSource(); const source = layer.getSource();
if (layerRenderer && source) { if (layerRenderer && source) {
const callback = forEachFeatureAtCoordinate.bind(null, layerState.managed); const callback = forEachFeatureAtCoordinate.bind(null, layerState.managed);
@@ -203,35 +180,6 @@ class MapRenderer extends Disposable {
return hasFeature !== undefined; return hasFeature !== undefined;
} }
/**
* @param {import("../layer/Layer.js").default} layer Layer.
* @protected
* @return {import("./Layer.js").default} Layer renderer. May return null.
*/
getLayerRenderer(layer) {
const layerKey = getUid(layer);
if (layerKey in this.layerRenderers_) {
return this.layerRenderers_[layerKey];
}
const renderer = layer.getRenderer();
if (!renderer) {
return null;
}
this.layerRenderers_[layerKey] = renderer;
this.layerRendererListeners_[layerKey] = listen(renderer, EventType.CHANGE, this.handleLayerRendererChange_, this);
return renderer;
}
/**
* @protected
* @return {Object<string, import("./Layer.js").default>} Layer renderers.
*/
getLayerRenderers() {
return this.layerRenderers_;
}
/** /**
* @return {import("../PluggableMap.js").default} Map. * @return {import("../PluggableMap.js").default} Map.
*/ */
@@ -239,29 +187,6 @@ class MapRenderer extends Disposable {
return this.map_; return this.map_;
} }
/**
* Handle changes in a layer renderer.
* @private
*/
handleLayerRendererChange_() {
this.map_.render();
}
/**
* @param {string} layerKey Layer key.
* @return {import("./Layer.js").default} Layer renderer.
* @private
*/
removeLayerRendererByKey_(layerKey) {
const layerRenderer = this.layerRenderers_[layerKey];
delete this.layerRenderers_[layerKey];
unlistenByKey(this.layerRendererListeners_[layerKey]);
delete this.layerRendererListeners_[layerKey];
return layerRenderer;
}
/** /**
* Render. * Render.
* @param {?import("../PluggableMap.js").FrameState} frameState Frame state. * @param {?import("../PluggableMap.js").FrameState} frameState Frame state.
@@ -279,21 +204,6 @@ class MapRenderer extends Disposable {
frameState.postRenderFunctions.push(expireIconCache); frameState.postRenderFunctions.push(expireIconCache);
} }
} }
/**
* @param {!import("../PluggableMap.js").FrameState} frameState Frame state.
* @protected
*/
scheduleRemoveUnusedLayerRenderers(frameState) {
const layerStatesMap = getLayerStatesMap(frameState.layerStatesArray);
for (const layerKey in this.layerRenderers_) {
if (!(layerKey in layerStatesMap)) {
frameState.postRenderFunctions.push(function() {
this.removeLayerRendererByKey_(layerKey).dispose();
}.bind(this));
}
}
}
} }
@@ -305,15 +215,4 @@ function expireIconCache(map, frameState) {
iconImageCache.expire(); iconImageCache.expire();
} }
/**
* @param {Array<import("../layer/Layer.js").State>} layerStatesArray Layer states array.
* @return {Object<string, import("../layer/Layer.js").State>} States mapped by layer uid.
*/
function getLayerStatesMap(layerStatesArray) {
return layerStatesArray.reduce(function(acc, state) {
acc[getUid(state.layer)] = state;
return acc;
}, {});
}
export default MapRenderer; export default MapRenderer;

View File

@@ -53,6 +53,13 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
super.disposeInternal(); super.disposeInternal();
} }
/**
* @inheritDoc
*/
handleFontsChanged() {
this.vectorRenderer_.handleFontsChanged();
}
/** /**
* @inheritDoc * @inheritDoc
*/ */

View File

@@ -3,10 +3,7 @@
*/ */
import {getUid} from '../../util.js'; import {getUid} from '../../util.js';
import ViewHint from '../../ViewHint.js'; import ViewHint from '../../ViewHint.js';
import {listen, unlisten} from '../../events.js';
import EventType from '../../events/EventType.js';
import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js';
import {labelCache} from '../../render/canvas.js';
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js'; import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
import CanvasLayerRenderer from './Layer.js'; import CanvasLayerRenderer from './Layer.js';
@@ -68,16 +65,6 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
* @type {boolean} * @type {boolean}
*/ */
this.replayGroupChanged = true; this.replayGroupChanged = true;
listen(labelCache, EventType.CLEAR, this.handleFontsChanged_, this);
}
/**
* @inheritDoc
*/
disposeInternal() {
unlisten(labelCache, EventType.CLEAR, this.handleFontsChanged_, this);
super.disposeInternal();
} }
/** /**
@@ -211,9 +198,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
} }
/** /**
* @param {import("../../events/Event.js").default} event Event. * @inheritDoc
*/ */
handleFontsChanged_(event) { handleFontsChanged() {
const layer = this.getLayer(); const layer = this.getLayer();
if (layer.getVisible() && this.replayGroup_) { if (layer.getVisible() && this.replayGroup_) {
layer.changed(); layer.changed();

View File

@@ -5,19 +5,17 @@ import {getUid} from '../../util.js';
import {createCanvasContext2D} from '../../dom.js'; import {createCanvasContext2D} from '../../dom.js';
import TileState from '../../TileState.js'; import TileState from '../../TileState.js';
import ViewHint from '../../ViewHint.js'; import ViewHint from '../../ViewHint.js';
import {listen, unlisten, unlistenByKey} from '../../events.js'; import {listen, unlistenByKey} from '../../events.js';
import EventType from '../../events/EventType.js'; import EventType from '../../events/EventType.js';
import {buffer, containsCoordinate, equals, getIntersection, getTopLeft, intersects} from '../../extent.js'; import {buffer, containsCoordinate, equals, getIntersection, intersects} from '../../extent.js';
import VectorTileRenderType from '../../layer/VectorTileRenderType.js'; import VectorTileRenderType from '../../layer/VectorTileRenderType.js';
import ReplayType from '../../render/canvas/BuilderType.js'; import ReplayType from '../../render/canvas/BuilderType.js';
import {labelCache} from '../../render/canvas.js';
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
import CanvasTileLayerRenderer from './TileLayer.js'; import CanvasTileLayerRenderer from './TileLayer.js';
import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
import { import {
apply as applyTransform, apply as applyTransform,
create as createTransform, create as createTransform,
compose as composeTransform,
reset as resetTransform, reset as resetTransform,
scale as scaleTransform, scale as scaleTransform,
translate as translateTransform, translate as translateTransform,
@@ -133,16 +131,12 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
// Use nearest lower resolution. // Use nearest lower resolution.
this.zDirection = 1; this.zDirection = 1;
listen(labelCache, EventType.CLEAR, this.handleFontsChanged_, this);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
disposeInternal() { disposeInternal() {
unlisten(labelCache, EventType.CLEAR, this.handleFontsChanged_, this);
this.overlayContext_.canvas.width = this.overlayContext_.canvas.height = 0; this.overlayContext_.canvas.width = this.overlayContext_.canvas.height = 0;
super.disposeInternal(); super.disposeInternal();
} }
@@ -364,38 +358,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
} }
/** /**
* @param {import("../../VectorTile.js").default} tile Tile. * @inheritDoc
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @return {import("../../transform.js").Transform} transform Transform.
* @private
*/ */
getReplayTransform_(tile, frameState) { handleFontsChanged() {
const layer = this.getLayer();
const source = layer.getSource();
const tileGrid = source.getTileGrid();
const tileCoord = tile.tileCoord;
const tileResolution = tileGrid.getResolution(tileCoord[0]);
const viewState = frameState.viewState;
const pixelRatio = frameState.pixelRatio;
const renderResolution = viewState.resolution / pixelRatio;
const tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
const center = viewState.center;
const origin = getTopLeft(tileExtent);
const size = frameState.size;
const offsetX = Math.round(pixelRatio * size[0] / 2);
const offsetY = Math.round(pixelRatio * size[1] / 2);
return composeTransform(this.tmpTransform_,
offsetX, offsetY,
tileResolution / renderResolution, tileResolution / renderResolution,
viewState.rotation,
(origin[0] - center[0]) / tileResolution,
(center[1] - origin[1]) / tileResolution);
}
/**
* @param {import("../../events/Event.js").default} event Event.
*/
handleFontsChanged_(event) {
const layer = this.getLayer(); const layer = this.getLayer();
if (layer.getVisible() && this.renderedLayerRevision_ !== undefined) { if (layer.getVisible() && this.renderedLayerRevision_ !== undefined) {
layer.changed(); layer.changed();

View File

@@ -0,0 +1,162 @@
/**
* @module ol/renderer/webgl/Layer
*/
import LayerRenderer from '../Layer.js';
import WebGLHelper from '../../webgl/Helper';
/**
* @typedef {Object} PostProcessesOptions
* @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than
* the main canvas that will then be sampled up (useful for saving resource on blur steps).
* @property {string} [vertexShader] Vertex shader source
* @property {string} [fragmentShader] Fragment shader source
* @property {Object.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process step
*/
/**
* @typedef {Object} Options
* @property {Object.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps
* @property {Array<PostProcessesOptions>} [postProcesses] Post-processes definitions
*/
/**
* @classdesc
* Base WebGL renderer class.
* Holds all logic related to data manipulation & some common rendering logic
*/
class WebGLLayerRenderer extends LayerRenderer {
/**
* @param {import("../../layer/Layer.js").default} layer Layer.
* @param {Options=} [opt_options] Options.
*/
constructor(layer, opt_options) {
super(layer);
const options = opt_options || {};
this.helper_ = new WebGLHelper({
postProcesses: options.postProcesses,
uniforms: options.uniforms
});
}
/**
* @inheritDoc
*/
disposeInternal() {
super.disposeInternal();
}
/**
* Will return the last shader compilation errors. If no error happened, will return null;
* @return {string|null} Errors, or null if last compilation was successful
* @api
*/
getShaderCompileErrors() {
return this.helper_.getShaderCompileErrors();
}
}
/**
* Pushes vertices and indices to the given buffers using the geometry coordinates and the following properties
* from the feature:
* - `color`
* - `opacity`
* - `size` (for points)
* - `u0`, `v0`, `u1`, `v1` (for points)
* - `rotateWithView` (for points)
* - `width` (for lines)
* Custom attributes can be designated using the `opt_attributes` argument, otherwise other properties on the
* feature will be ignored.
* @param {import("../../webgl/Buffer").default} vertexBuffer WebGL buffer in which new vertices will be pushed.
* @param {import("../../webgl/Buffer").default} indexBuffer WebGL buffer in which new indices will be pushed.
* @param {import("../../format/GeoJSON").GeoJSONFeature} geojsonFeature Feature in geojson format, coordinates
* expressed in EPSG:4326.
* @param {Array<string>} [opt_attributes] Custom attributes. An array of properties which will be read from the
* feature and pushed in the buffer in the given order. Note: attributes can only be numerical! Any other type or
* NaN will result in `0` being pushed in the buffer.
*/
export function pushFeatureToBuffer(vertexBuffer, indexBuffer, geojsonFeature, opt_attributes) {
if (!geojsonFeature.geometry) {
return;
}
switch (geojsonFeature.geometry.type) {
case 'Point':
pushPointFeatureToBuffer_(vertexBuffer, indexBuffer, geojsonFeature, opt_attributes);
return;
default:
return;
}
}
const tmpArray_ = [];
/**
* Pushes a quad (two triangles) based on a point geometry
* @param {import("../../webgl/Buffer").default} vertexBuffer WebGL buffer
* @param {import("../../webgl/Buffer").default} indexBuffer WebGL buffer
* @param {import("../../format/GeoJSON").GeoJSONFeature} geojsonFeature Feature
* @param {Array<string>} [opt_attributes] Custom attributes
* @private
*/
function pushPointFeatureToBuffer_(vertexBuffer, indexBuffer, geojsonFeature, opt_attributes) {
const stride = 12 + (opt_attributes !== undefined ? opt_attributes.length : 0);
const x = geojsonFeature.geometry.coordinates[0];
const y = geojsonFeature.geometry.coordinates[1];
const u0 = geojsonFeature.properties.u0;
const v0 = geojsonFeature.properties.v0;
const u1 = geojsonFeature.properties.u1;
const v1 = geojsonFeature.properties.v1;
const size = geojsonFeature.properties.size;
const opacity = geojsonFeature.properties.opacity;
const rotateWithView = geojsonFeature.properties.rotateWithView;
const color = geojsonFeature.properties.color;
const red = color[0];
const green = color[1];
const blue = color[2];
const alpha = color[3];
const baseIndex = vertexBuffer.getArray().length / stride;
// read custom numerical attributes on the feature
const customAttributeValues = tmpArray_;
customAttributeValues.length = opt_attributes ? opt_attributes.length : 0;
for (let i = 0; i < customAttributeValues.length; i++) {
customAttributeValues[i] = parseFloat(geojsonFeature.properties[opt_attributes[i]]) || 0;
}
// push vertices for each of the four quad corners (first standard then custom attributes)
vertexBuffer.getArray().push(x, y, -size / 2, -size / 2, u0, v0, opacity, rotateWithView, red, green, blue, alpha);
Array.prototype.push.apply(vertexBuffer.getArray(), customAttributeValues);
vertexBuffer.getArray().push(x, y, +size / 2, -size / 2, u1, v0, opacity, rotateWithView, red, green, blue, alpha);
Array.prototype.push.apply(vertexBuffer.getArray(), customAttributeValues);
vertexBuffer.getArray().push(x, y, +size / 2, +size / 2, u1, v1, opacity, rotateWithView, red, green, blue, alpha);
Array.prototype.push.apply(vertexBuffer.getArray(), customAttributeValues);
vertexBuffer.getArray().push(x, y, -size / 2, +size / 2, u0, v1, opacity, rotateWithView, red, green, blue, alpha);
Array.prototype.push.apply(vertexBuffer.getArray(), customAttributeValues);
indexBuffer.getArray().push(
baseIndex, baseIndex + 1, baseIndex + 3,
baseIndex + 1, baseIndex + 2, baseIndex + 3
);
}
/**
* Returns a texture of 1x1 pixel, white
* @private
* @return {ImageData} Image data.
*/
export function getBlankTexture() {
const canvas = document.createElement('canvas');
const image = canvas.getContext('2d').createImageData(1, 1);
image.data[0] = image.data[1] = image.data[2] = image.data[3] = 255;
return image;
}
export default WebGLLayerRenderer;

View File

@@ -1,11 +1,21 @@
/** /**
* @module ol/renderer/webgl/PointsLayer * @module ol/renderer/webgl/PointsLayer
*/ */
import LayerRenderer from '../Layer';
import WebGLArrayBuffer from '../../webgl/Buffer'; import WebGLArrayBuffer from '../../webgl/Buffer';
import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT} from '../../webgl'; import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT} from '../../webgl';
import WebGLHelper, {DefaultAttrib} from '../../webgl/Helper'; import {DefaultAttrib, DefaultUniform} from '../../webgl/Helper';
import GeometryType from '../../geom/GeometryType'; import GeometryType from '../../geom/GeometryType';
import WebGLLayerRenderer, {getBlankTexture, pushFeatureToBuffer} from './Layer';
import GeoJSON from '../../format/GeoJSON';
import {getUid} from '../../util';
import ViewHint from '../../ViewHint';
import {createEmpty, equals} from '../../extent';
import {
create as createTransform,
makeInverse as makeInverseTransform,
multiply as multiplyTransform,
apply as applyTransform
} from '../../transform';
const VERTEX_SHADER = ` const VERTEX_SHADER = `
precision mediump float; precision mediump float;
@@ -55,15 +65,6 @@ const FRAGMENT_SHADER = `
gl_FragColor.rgb *= gl_FragColor.a; gl_FragColor.rgb *= gl_FragColor.a;
}`; }`;
/**
* @typedef {Object} PostProcessesOptions
* @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than
* the main canvas that will then be sampled up (useful for saving resource on blur steps).
* @property {string} [vertexShader] Vertex shader source
* @property {string} [fragmentShader] Fragment shader source
* @property {Object.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process step
*/
/** /**
* @typedef {Object} Options * @typedef {Object} Options
* @property {function(import("../../Feature").default):number} [sizeCallback] Will be called on every feature in the * @property {function(import("../../Feature").default):number} [sizeCallback] Will be called on every feature in the
@@ -91,7 +92,7 @@ const FRAGMENT_SHADER = `
* @property {string} [fragmentShader] Fragment shader source * @property {string} [fragmentShader] Fragment shader source
* @property {Object.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps * @property {Object.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps
* Please note that `u_texture` is reserved for the main texture slot. * Please note that `u_texture` is reserved for the main texture slot.
* @property {Array<PostProcessesOptions>} [postProcesses] Post-processes definitions * @property {Array<import("./Layer").PostProcessesOptions>} [postProcesses] Post-processes definitions
*/ */
/** /**
@@ -186,22 +187,23 @@ const FRAGMENT_SHADER = `
* *
* @api * @api
*/ */
class WebGLPointsLayerRenderer extends LayerRenderer { class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
/** /**
* @param {import("../../layer/Vector.js").default} vectorLayer Vector layer. * @param {import("../../layer/Vector.js").default} vectorLayer Vector layer.
* @param {Options=} [opt_options] Options. * @param {Options=} [opt_options] Options.
*/ */
constructor(vectorLayer, opt_options) { constructor(vectorLayer, opt_options) {
super(vectorLayer);
const options = opt_options || {}; const options = opt_options || {};
const uniforms = options.uniforms || {}; const uniforms = options.uniforms || {};
uniforms.u_texture = options.texture || this.getDefaultTexture(); uniforms.u_texture = options.texture || getBlankTexture();
this.helper_ = new WebGLHelper({ const projectionMatrixTransform = createTransform();
postProcesses: options.postProcesses, uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform;
uniforms: uniforms
super(vectorLayer, {
uniforms: uniforms,
postProcesses: options.postProcesses
}); });
this.sourceRevision_ = -1; this.sourceRevision_ = -1;
@@ -238,6 +240,38 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
this.rotateWithViewCallback_ = options.rotateWithViewCallback || function() { this.rotateWithViewCallback_ = options.rotateWithViewCallback || function() {
return false; return false;
}; };
this.geojsonFormat_ = new GeoJSON();
/**
* @type {Object<string, import("../../format/GeoJSON").GeoJSONFeature>}
* @private
*/
this.geojsonFeatureCache_ = {};
this.previousExtent_ = createEmpty();
/**
* This transform is updated on every frame and is the composition of:
* - invert of the world->screen transform that was used when rebuilding buffers (see `this.renderTransform_`)
* - current world->screen transform
* @type {import("../../transform.js").Transform}
* @private
*/
this.currentTransform_ = projectionMatrixTransform;
/**
* This transform is updated when buffers are rebuilt and converts world space coordinates to screen space
* @type {import("../../transform.js").Transform}
* @private
*/
this.renderTransform_ = createTransform();
/**
* @type {import("../../transform.js").Transform}
* @private
*/
this.invertRenderTransform_ = createTransform();
} }
/** /**
@@ -269,58 +303,35 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
prepareFrame(frameState) { prepareFrame(frameState) {
const vectorLayer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()); const vectorLayer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer());
const vectorSource = vectorLayer.getSource(); const vectorSource = vectorLayer.getSource();
const viewState = frameState.viewState;
// TODO: get this from somewhere...
const stride = 12; const stride = 12;
this.helper_.prepareDraw(frameState); // the source has changed: clear the feature cache & reload features
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
if (this.sourceRevision_ < vectorSource.getRevision()) { if (sourceChanged) {
this.sourceRevision_ = vectorSource.getRevision(); this.sourceRevision_ = vectorSource.getRevision();
this.verticesBuffer_.getArray().length = 0; this.geojsonFeatureCache_ = {};
this.indicesBuffer_.getArray().length = 0;
const viewState = frameState.viewState;
const projection = viewState.projection; const projection = viewState.projection;
const resolution = viewState.resolution; const resolution = viewState.resolution;
// loop on features to fill the buffer
vectorSource.loadFeatures([-Infinity, -Infinity, Infinity, Infinity], resolution, projection); vectorSource.loadFeatures([-Infinity, -Infinity, Infinity, Infinity], resolution, projection);
vectorSource.forEachFeature((feature) => {
if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) {
return;
}
const x = this.coordCallback_(feature, 0);
const y = this.coordCallback_(feature, 1);
const u0 = this.texCoordCallback_(feature, 0);
const v0 = this.texCoordCallback_(feature, 1);
const u1 = this.texCoordCallback_(feature, 2);
const v1 = this.texCoordCallback_(feature, 3);
const size = this.sizeCallback_(feature);
const opacity = this.opacityCallback_(feature);
const rotateWithView = this.rotateWithViewCallback_(feature) ? 1 : 0;
const color = this.colorCallback_(feature, this.colorArray_);
const red = color[0];
const green = color[1];
const blue = color[2];
const alpha = color[3];
const baseIndex = this.verticesBuffer_.getArray().length / stride;
this.verticesBuffer_.getArray().push(
x, y, -size / 2, -size / 2, u0, v0, opacity, rotateWithView, red, green, blue, alpha,
x, y, +size / 2, -size / 2, u1, v0, opacity, rotateWithView, red, green, blue, alpha,
x, y, +size / 2, +size / 2, u1, v1, opacity, rotateWithView, red, green, blue, alpha,
x, y, -size / 2, +size / 2, u0, v1, opacity, rotateWithView, red, green, blue, alpha
);
this.indicesBuffer_.getArray().push(
baseIndex, baseIndex + 1, baseIndex + 3,
baseIndex + 1, baseIndex + 2, baseIndex + 3
);
});
this.helper_.flushBufferData(ARRAY_BUFFER, this.verticesBuffer_);
this.helper_.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
} }
const viewNotMoving = !frameState.viewHints[ViewHint.ANIMATING] && !frameState.viewHints[ViewHint.INTERACTING];
const extentChanged = !equals(this.previousExtent_, frameState.extent);
if ((sourceChanged || extentChanged) && viewNotMoving) {
this.rebuildBuffers_(frameState);
this.previousExtent_ = frameState.extent.slice();
}
// apply the current projection transform with the invert of the one used to fill buffers
this.helper_.makeProjectionTransform(frameState, this.currentTransform_);
multiplyTransform(this.currentTransform_, this.invertRenderTransform_);
this.helper_.prepareDraw(frameState);
// write new data // write new data
this.helper_.bindBuffer(ARRAY_BUFFER, this.verticesBuffer_); this.helper_.bindBuffer(ARRAY_BUFFER, this.verticesBuffer_);
this.helper_.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); this.helper_.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
@@ -337,24 +348,54 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
} }
/** /**
* Will return the last shader compilation errors. If no error happened, will return null; * Rebuild internal webgl buffers based on current view extent; costly, should not be called too much
* @return {string|null} Errors, or null if last compilation was successful * @param {import("../../PluggableMap").FrameState} frameState Frame state.
* @api
*/
getShaderCompileErrors() {
return this.helper_.getShaderCompileErrors();
}
/**
* Returns a texture of 1x1 pixel, white
* @private * @private
* @return {ImageData} Image data.
*/ */
getDefaultTexture() { rebuildBuffers_(frameState) {
const canvas = document.createElement('canvas'); const vectorLayer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer());
const image = canvas.getContext('2d').createImageData(1, 1); const vectorSource = vectorLayer.getSource();
image.data[0] = image.data[1] = image.data[2] = image.data[3] = 255;
return image; this.verticesBuffer_.getArray().length = 0;
this.indicesBuffer_.getArray().length = 0;
// saves the projection transform for the current frame state
this.helper_.makeProjectionTransform(frameState, this.renderTransform_);
makeInverseTransform(this.invertRenderTransform_, this.renderTransform_);
// loop on features to fill the buffer
const features = vectorSource.getFeatures();
let feature;
for (let i = 0; i < features.length; i++) {
feature = features[i];
if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) {
continue;
}
let geojsonFeature = this.geojsonFeatureCache_[getUid(feature)];
if (!geojsonFeature) {
geojsonFeature = this.geojsonFormat_.writeFeatureObject(feature);
this.geojsonFeatureCache_[getUid(feature)] = geojsonFeature;
}
geojsonFeature.geometry.coordinates[0] = this.coordCallback_(feature, 0);
geojsonFeature.geometry.coordinates[1] = this.coordCallback_(feature, 1);
applyTransform(this.renderTransform_, geojsonFeature.geometry.coordinates);
geojsonFeature.properties = geojsonFeature.properties || {};
geojsonFeature.properties.color = this.colorCallback_(feature, this.colorArray_);
geojsonFeature.properties.u0 = this.texCoordCallback_(feature, 0);
geojsonFeature.properties.v0 = this.texCoordCallback_(feature, 1);
geojsonFeature.properties.u1 = this.texCoordCallback_(feature, 2);
geojsonFeature.properties.v1 = this.texCoordCallback_(feature, 3);
geojsonFeature.properties.size = this.sizeCallback_(feature);
geojsonFeature.properties.opacity = this.opacityCallback_(feature);
geojsonFeature.properties.rotateWithView = this.rotateWithViewCallback_(feature) ? 1 : 0;
pushFeatureToBuffer(this.verticesBuffer_, this.indicesBuffer_, geojsonFeature);
}
this.helper_.flushBufferData(ARRAY_BUFFER, this.verticesBuffer_);
this.helper_.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
} }
} }

View File

@@ -2,7 +2,7 @@
* @module ol/structs/RBush * @module ol/structs/RBush
*/ */
import {getUid} from '../util.js'; import {getUid} from '../util.js';
import rbush from 'rbush'; import RBush_ from 'rbush';
import {createOrUpdate, equals} from '../extent.js'; import {createOrUpdate, equals} from '../extent.js';
import {isEmpty} from '../obj.js'; import {isEmpty} from '../obj.js';
@@ -31,7 +31,7 @@ class RBush {
/** /**
* @private * @private
*/ */
this.rbush_ = rbush(opt_maxEntries, undefined); this.rbush_ = new RBush_(opt_maxEntries);
/** /**
* A mapping between the objects added to this rbush wrapper * A mapping between the objects added to this rbush wrapper

View File

@@ -3,11 +3,12 @@
*/ */
import {createCanvasContext2D} from '../dom.js'; import {createCanvasContext2D} from '../dom.js';
import {listenOnce, unlistenByKey} from '../events.js';
import EventTarget from '../events/Target.js'; import EventTarget from '../events/Target.js';
import EventType from '../events/EventType.js'; import EventType from '../events/EventType.js';
import ImageState from '../ImageState.js'; import ImageState from '../ImageState.js';
import {shared as iconImageCache} from './IconImageCache.js'; import {shared as iconImageCache} from './IconImageCache.js';
import {listenImage} from '../Image.js';
class IconImage extends EventTarget { class IconImage extends EventTarget {
/** /**
@@ -52,9 +53,9 @@ class IconImage extends EventTarget {
/** /**
* @private * @private
* @type {Array<import("../events.js").EventsKey>} * @type {function():void}
*/ */
this.imageListenerKeys_ = null; this.unlisten_ = null;
/** /**
* @private * @private
@@ -185,17 +186,16 @@ class IconImage extends EventTarget {
load() { load() {
if (this.imageState_ == ImageState.IDLE) { if (this.imageState_ == ImageState.IDLE) {
this.imageState_ = ImageState.LOADING; this.imageState_ = ImageState.LOADING;
this.imageListenerKeys_ = [
listenOnce(this.image_, EventType.ERROR,
this.handleImageError_, this),
listenOnce(this.image_, EventType.LOAD,
this.handleImageLoad_, this)
];
try { try {
/** @type {HTMLImageElement} */ (this.image_).src = this.src_; /** @type {HTMLImageElement} */ (this.image_).src = this.src_;
} catch (e) { } catch (e) {
this.handleImageError_(); this.handleImageError_();
} }
this.unlisten_ = listenImage(
this.image_,
this.handleImageLoad_.bind(this),
this.handleImageError_.bind(this)
);
} }
} }
@@ -233,8 +233,10 @@ class IconImage extends EventTarget {
* @private * @private
*/ */
unlistenImage_() { unlistenImage_() {
this.imageListenerKeys_.forEach(unlistenByKey); if (this.unlisten_) {
this.imageListenerKeys_ = null; this.unlisten_();
this.unlisten_ = null;
}
} }
} }

View File

@@ -25,7 +25,6 @@ let uidCounter_ = 0;
* *
* @param {Object} obj The object to get the unique ID for. * @param {Object} obj The object to get the unique ID for.
* @return {string} The unique ID for the object. * @return {string} The unique ID for the object.
* @function module:ol.getUid
* @api * @api
*/ */
export function getUid(obj) { export function getUid(obj) {

View File

@@ -62,7 +62,7 @@ export const DefaultAttrib = {
}; };
/** /**
* @typedef {number|Array<number>|HTMLCanvasElement|HTMLImageElement|ImageData} UniformLiteralValue * @typedef {number|Array<number>|HTMLCanvasElement|HTMLImageElement|ImageData|import("../transform").Transform} UniformLiteralValue
*/ */
/** /**
@@ -273,12 +273,6 @@ class WebGLHelper extends Disposable {
listen(this.canvas_, ContextEventType.RESTORED, listen(this.canvas_, ContextEventType.RESTORED,
this.handleWebGLContextRestored, this); this.handleWebGLContextRestored, this);
/**
* @private
* @type {import("../transform.js").Transform}
*/
this.projectionMatrix_ = createTransform();
/** /**
* @private * @private
* @type {import("../transform.js").Transform} * @type {import("../transform.js").Transform}
@@ -514,14 +508,6 @@ class WebGLHelper extends Disposable {
applyFrameState(frameState) { applyFrameState(frameState) {
const size = frameState.size; const size = frameState.size;
const rotation = frameState.viewState.rotation; const rotation = frameState.viewState.rotation;
const resolution = frameState.viewState.resolution;
const center = frameState.viewState.center;
// set the "uniform" values (coordinates 0,0 are the center of the view)
const projectionMatrix = resetTransform(this.projectionMatrix_);
scaleTransform(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1]));
rotateTransform(projectionMatrix, -rotation);
translateTransform(projectionMatrix, -center[0], -center[1]);
const offsetScaleMatrix = resetTransform(this.offsetScaleMatrix_); const offsetScaleMatrix = resetTransform(this.offsetScaleMatrix_);
scaleTransform(offsetScaleMatrix, 2 / size[0], 2 / size[1]); scaleTransform(offsetScaleMatrix, 2 / size[0], 2 / size[1]);
@@ -531,7 +517,6 @@ class WebGLHelper extends Disposable {
rotateTransform(offsetRotateMatrix, -rotation); rotateTransform(offsetRotateMatrix, -rotation);
} }
this.setUniformMatrixValue(DefaultUniform.PROJECTION_MATRIX, fromTransform(this.tmpMat4_, projectionMatrix));
this.setUniformMatrixValue(DefaultUniform.OFFSET_SCALE_MATRIX, fromTransform(this.tmpMat4_, offsetScaleMatrix)); this.setUniformMatrixValue(DefaultUniform.OFFSET_SCALE_MATRIX, fromTransform(this.tmpMat4_, offsetScaleMatrix));
this.setUniformMatrixValue(DefaultUniform.OFFSET_ROTATION_MATRIX, fromTransform(this.tmpMat4_, offsetRotateMatrix)); this.setUniformMatrixValue(DefaultUniform.OFFSET_ROTATION_MATRIX, fromTransform(this.tmpMat4_, offsetRotateMatrix));
} }
@@ -565,7 +550,9 @@ class WebGLHelper extends Disposable {
// fill texture slots by increasing index // fill texture slots by increasing index
gl.uniform1i(this.getUniformLocation(uniform.name), textureSlot++); gl.uniform1i(this.getUniformLocation(uniform.name), textureSlot++);
} else if (Array.isArray(value)) { } else if (Array.isArray(value) && value.length === 6) {
this.setUniformMatrixValue(uniform.name, fromTransform(this.tmpMat4_, value));
} else if (Array.isArray(value) && value.length <= 4) {
switch (value.length) { switch (value.length) {
case 2: case 2:
gl.uniform2f(this.getUniformLocation(uniform.name), value[0], value[1]); gl.uniform2f(this.getUniformLocation(uniform.name), value[0], value[1]);
@@ -689,6 +676,28 @@ class WebGLHelper extends Disposable {
return this.attribLocations_[name]; return this.attribLocations_[name];
} }
/**
* Modifies the given transform to apply the rotation/translation/scaling of the given frame state.
* The resulting transform can be used to convert world space coordinates to view coordinates.
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
* @param {import("../transform").Transform} transform Transform to update.
* @return {import("../transform").Transform} The updated transform object.
* @api
*/
makeProjectionTransform(frameState, transform) {
const size = frameState.size;
const rotation = frameState.viewState.rotation;
const resolution = frameState.viewState.resolution;
const center = frameState.viewState.center;
resetTransform(transform);
scaleTransform(transform, 2 / (resolution * size[0]), 2 / (resolution * size[1]));
rotateTransform(transform, -rotation);
translateTransform(transform, -center[0], -center[1]);
return transform;
}
/** /**
* Give a value for a standard float uniform * Give a value for a standard float uniform
* @param {string} uniform Uniform name * @param {string} uniform Uniform name

12
src/ol/worker/version.js Normal file
View File

@@ -0,0 +1,12 @@
/**
* A worker that responds to messages by posting a message with the version identifer.
* @module ol/worker/version
*/
import {VERSION} from '../util';
onmessage = event => {
console.log('version worker received message:', event.data); // eslint-disable-line
postMessage(`version: ${VERSION}`);
};
export let create;

View File

@@ -76,7 +76,7 @@ main() {
npm install npm install
npm run build-package npm run build-package
cd ${BUILT_PACKAGE} cd ${BUILT_PACKAGE}
npm publish --tag beta npm publish
} }
if test ${#} -ne 1; then if test ${#} -ne 1; then

View File

@@ -0,0 +1,97 @@
const path = require('path');
const babel = require('rollup-plugin-babel');
const resolve = require('rollup-plugin-node-resolve');
const common = require('rollup-plugin-commonjs');
const rollup = require('rollup');
const terser = require('rollup-plugin-terser').terser;
const fse = require('fs-extra');
async function build(input, {minify = true} = {}) {
const plugins = [
{
name: 'remove export let create',
transform(code, id) {
if (id !== input) {
return null;
}
return code.replace('export let create;\n', '');
}
},
common(),
resolve(),
babel({
'presets': [
[
'@babel/preset-env',
{
'modules': false,
'targets': 'last 2 version, not dead'
}
]
]
})
];
if (minify) {
plugins.push(terser());
}
plugins.push({
name: 'serialize worker and export create function',
renderChunk(code) {
return `
const source = ${JSON.stringify(code)};
const blob = new Blob([source], {type: 'application/javascript'});
const url = URL.createObjectURL(blob);
export function create() {
return new Worker(url);
}
`;
}
});
const bundle = await rollup.rollup({input, plugins});
const {output} = await bundle.generate({format: 'es'});
if (output.length !== 1) {
throw new Error(`Unexpected output length: ${output.length}`);
}
const chunk = output[0];
if (chunk.isAsset) {
throw new Error('Expected a chunk, got an asset');
}
return chunk;
}
exports.build = build;
/**
* Creates modules with inlined versions of the worker sources. These modules
* export a `create` function for creating a worker.
*/
async function main() {
const inputDir = path.join(__dirname, '../src/ol/worker');
const outputDir = path.join(__dirname, '../build/ol/src/worker');
await fse.ensureDir(outputDir);
const entries = await fse.readdir(inputDir);
for (const entry of entries) {
if (!entry.endsWith('.js')) {
continue;
}
const chunk = await build(path.join(inputDir, entry));
await fse.writeFile(path.join(outputDir, entry), chunk.code);
}
}
if (require.main === module) {
main().catch(err => {
process.stderr.write(`${err.stack}\n`);
process.exit(1);
});
}

View File

@@ -86,6 +86,14 @@ module.exports = function(karma) {
}, },
include: path.resolve('src/ol/'), include: path.resolve('src/ol/'),
exclude: path.resolve('node_modules/') exclude: path.resolve('node_modules/')
}, {
test: /\.js$/,
use: {
loader: path.join(__dirname, '../examples/webpack/worker-loader.js')
},
include: [
path.join(__dirname, '../src/ol/worker')
]
} }
] ]
} }

View File

@@ -1597,6 +1597,39 @@ describe('ol.format.KML', function() {
expect(node).to.xmleql(parse(text)); expect(node).to.xmleql(parse(text));
}); });
it('can write ExtendedData after Style tag', function() {
const style = new Style({
stroke: new Stroke({
color: '#112233',
width: 2
})
});
const feature = new Feature();
feature.set('foo', null);
feature.setStyle([style]);
const features = [feature];
const node = format.writeFeaturesNode(features);
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>' +
' </Style>' +
' <ExtendedData>' +
' <Data name="foo"/>' +
' </ExtendedData>' +
' </Placemark>' +
'</kml>';
expect(node).to.xmleql(parse(text));
});
it('can read ExtendedData', function() { it('can read ExtendedData', function() {
const text = const text =
'<kml xmlns="http://earth.google.com/kml/2.2">' + '<kml xmlns="http://earth.google.com/kml/2.2">' +

View File

@@ -72,9 +72,11 @@ describe('ol.geom.flat.drawTextOnPath', function() {
const instructions = drawTextOnPath( const instructions = drawTextOnPath(
angled, 0, angled.length, 2, 'foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {}); angled, 0, angled.length, 2, 'foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {});
expect(instructions[0][3]).to.eql(45 * Math.PI / 180); expect(instructions[0][3]).to.eql(45 * Math.PI / 180);
expect(instructions[0][4]).to.be('fo'); expect(instructions[0][4]).to.be('f');
expect(instructions[1][3]).to.eql(-45 * Math.PI / 180); expect(instructions[1][3]).to.eql(45 * Math.PI / 180);
expect(instructions[1][4]).to.be('o'); expect(instructions[1][4]).to.be('o');
expect(instructions[2][3]).to.eql(-45 * Math.PI / 180);
expect(instructions[2][4]).to.be('o');
}); });
it('respects maxAngle', function() { it('respects maxAngle', function() {

View File

@@ -0,0 +1,46 @@
import {listenImage} from '../../../src/ol/Image.js';
describe('HTML Image loading', function() {
let handleLoad, handleError, img;
beforeEach(function() {
handleLoad = sinon.spy();
handleError = sinon.spy();
img = new Image();
});
it('handles load event', function(done) {
img.src = 'spec/ol/data/dot.png';
listenImage(img, handleLoad, handleError);
setTimeout(function() {
expect(handleLoad).to.be.called();
expect(handleError).not.to.be.called();
done();
}, 200);
});
it('handles error event', function(done) {
img.src = 'invalid.jpeg';
listenImage(img, handleLoad, handleError);
setTimeout(function() {
expect(handleLoad).not.to.be.called();
expect(handleError).to.be.called();
done();
}, 200);
});
it('handles cancelation', function(done) {
img.src = 'spec/ol/data/dot.png';
listenImage(img, handleLoad, handleError)();
setTimeout(function() {
expect(handleLoad).not.to.be.called();
expect(handleError).not.to.be.called();
done();
}, 200);
});
});

View File

@@ -1,4 +1,4 @@
import {clamp, lerp, cosh, roundUpToPowerOfTwo, solveLinearSystem, toDegrees, toRadians, modulo} from '../../../src/ol/math.js'; import {clamp, lerp, cosh, solveLinearSystem, toDegrees, toRadians, modulo} from '../../../src/ol/math.js';
describe('ol.math.clamp', function() { describe('ol.math.clamp', function() {
@@ -49,45 +49,6 @@ describe('ol.math.cosh', function() {
}); });
describe('ol.math.roundUpToPowerOfTwo', function() {
it('raises an exception when x is negative', function() {
expect(function() {
roundUpToPowerOfTwo(-1);
}).to.throwException();
});
it('raises an exception when x is zero', function() {
expect(function() {
roundUpToPowerOfTwo(0);
}).to.throwException();
});
it('returns the expected value for simple powers of two', function() {
expect(roundUpToPowerOfTwo(1)).to.be(1);
expect(roundUpToPowerOfTwo(2)).to.be(2);
expect(roundUpToPowerOfTwo(4)).to.be(4);
expect(roundUpToPowerOfTwo(8)).to.be(8);
expect(roundUpToPowerOfTwo(16)).to.be(16);
expect(roundUpToPowerOfTwo(32)).to.be(32);
expect(roundUpToPowerOfTwo(64)).to.be(64);
expect(roundUpToPowerOfTwo(128)).to.be(128);
expect(roundUpToPowerOfTwo(256)).to.be(256);
});
it('returns the expected value for simple powers of ten', function() {
expect(roundUpToPowerOfTwo(1)).to.be(1);
expect(roundUpToPowerOfTwo(10)).to.be(16);
expect(roundUpToPowerOfTwo(100)).to.be(128);
expect(roundUpToPowerOfTwo(1000)).to.be(1024);
expect(roundUpToPowerOfTwo(10000)).to.be(16384);
expect(roundUpToPowerOfTwo(100000)).to.be(131072);
expect(roundUpToPowerOfTwo(1000000)).to.be(1048576);
expect(roundUpToPowerOfTwo(10000000)).to.be(16777216);
});
});
describe('ol.math.solveLinearSystem', function() { describe('ol.math.solveLinearSystem', function() {
it('calculates correctly', function() { it('calculates correctly', function() {

View File

@@ -49,7 +49,7 @@ describe('ol.renderer.canvas.TileLayer', function() {
}); });
source.updateParams({TIME: '1'}); source.updateParams({TIME: '1'});
map.renderSync(); map.renderSync();
const tiles = map.getRenderer().getLayerRenderer(layer).renderedTiles; const tiles = layer.getRenderer().renderedTiles;
expect(tiles.length).to.be(1); expect(tiles.length).to.be(1);
expect(tiles[0]).to.equal(tile); expect(tiles[0]).to.equal(tile);
expect(tile.inTransition()).to.be(true); expect(tile.inTransition()).to.be(true);

View File

@@ -75,7 +75,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
style: layerStyle style: layerStyle
}); });
map.addLayer(layer); map.addLayer(layer);
const spy = sinon.spy(map.getRenderer().getLayerRenderer(layer), const spy = sinon.spy(layer.getRenderer(),
'renderFeature'); 'renderFeature');
map.renderSync(); map.renderSync();
expect(spy.getCall(0).args[3]).to.be(layerStyle); expect(spy.getCall(0).args[3]).to.be(layerStyle);

View File

@@ -140,7 +140,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
}); });
it('gives precedence to feature styles over layer styles', function() { it('gives precedence to feature styles over layer styles', function() {
const spy = sinon.spy(map.getRenderer().getLayerRenderer(layer), const spy = sinon.spy(layer.getRenderer(),
'renderFeature'); 'renderFeature');
map.renderSync(); map.renderSync();
expect(spy.getCall(0).args[2]).to.be(layer.getStyle()); expect(spy.getCall(0).args[2]).to.be(layer.getStyle());

View File

@@ -0,0 +1,150 @@
import WebGLLayerRenderer, {getBlankTexture, pushFeatureToBuffer} from '../../../../../src/ol/renderer/webgl/Layer';
import WebGLArrayBuffer from '../../../../../src/ol/webgl/Buffer';
import Layer from '../../../../../src/ol/layer/Layer';
describe('ol.renderer.webgl.Layer', function() {
describe('constructor', function() {
let target;
beforeEach(function() {
target = document.createElement('div');
target.style.width = '256px';
target.style.height = '256px';
document.body.appendChild(target);
});
afterEach(function() {
document.body.removeChild(target);
});
it('creates a new instance', function() {
const layer = new Layer({});
const renderer = new WebGLLayerRenderer(layer);
expect(renderer).to.be.a(WebGLLayerRenderer);
});
});
describe('pushFeatureToBuffer', function() {
let vertexBuffer, indexBuffer;
beforeEach(function() {
vertexBuffer = new WebGLArrayBuffer();
indexBuffer = new WebGLArrayBuffer();
});
it('does nothing if the feature has no geometry', function() {
const feature = {
type: 'Feature',
id: 'AFG',
properties: {
color: [0.5, 1, 0.2, 0.7],
size: 3
},
geometry: null
};
pushFeatureToBuffer(vertexBuffer, indexBuffer, feature);
expect(vertexBuffer.getArray().length).to.eql(0);
expect(indexBuffer.getArray().length).to.eql(0);
});
it('adds two triangles with the correct attributes for a point geometry', function() {
const feature = {
type: 'Feature',
id: 'AFG',
properties: {
color: [0.5, 1, 0.2, 0.7],
size: 3
},
geometry: {
type: 'Point',
coordinates: [-75, 47]
}
};
const attributePerVertex = 12;
pushFeatureToBuffer(vertexBuffer, indexBuffer, feature);
expect(vertexBuffer.getArray().length).to.eql(attributePerVertex * 4);
expect(indexBuffer.getArray().length).to.eql(6);
});
it('correctly sets indices & coordinates for several features', function() {
const feature = {
type: 'Feature',
id: 'AFG',
properties: {
color: [0.5, 1, 0.2, 0.7],
size: 3
},
geometry: {
type: 'Point',
coordinates: [-75, 47]
}
};
const attributePerVertex = 12;
pushFeatureToBuffer(vertexBuffer, indexBuffer, feature);
pushFeatureToBuffer(vertexBuffer, indexBuffer, feature);
expect(vertexBuffer.getArray()[0]).to.eql(-75);
expect(vertexBuffer.getArray()[1]).to.eql(47);
expect(vertexBuffer.getArray()[0 + attributePerVertex]).to.eql(-75);
expect(vertexBuffer.getArray()[1 + attributePerVertex]).to.eql(47);
// first point
expect(indexBuffer.getArray()[0]).to.eql(0);
expect(indexBuffer.getArray()[1]).to.eql(1);
expect(indexBuffer.getArray()[2]).to.eql(3);
expect(indexBuffer.getArray()[3]).to.eql(1);
expect(indexBuffer.getArray()[4]).to.eql(2);
expect(indexBuffer.getArray()[5]).to.eql(3);
// second point
expect(indexBuffer.getArray()[6]).to.eql(4);
expect(indexBuffer.getArray()[7]).to.eql(5);
expect(indexBuffer.getArray()[8]).to.eql(7);
expect(indexBuffer.getArray()[9]).to.eql(5);
expect(indexBuffer.getArray()[10]).to.eql(6);
expect(indexBuffer.getArray()[11]).to.eql(7);
});
it('correctly adds custom attributes', function() {
const feature = {
type: 'Feature',
id: 'AFG',
properties: {
color: [0.5, 1, 0.2, 0.7],
custom: 4,
customString: '5',
custom2: 12.4,
customString2: 'abc'
},
geometry: {
type: 'Point',
coordinates: [-75, 47]
}
};
const attributePerVertex = 16;
pushFeatureToBuffer(vertexBuffer, indexBuffer, feature, ['custom', 'custom2', 'customString', 'customString2']);
expect(vertexBuffer.getArray().length).to.eql(attributePerVertex * 4);
expect(indexBuffer.getArray().length).to.eql(6);
expect(vertexBuffer.getArray()[12]).to.eql(4);
expect(vertexBuffer.getArray()[13]).to.eql(12.4);
expect(vertexBuffer.getArray()[14]).to.eql(5);
expect(vertexBuffer.getArray()[15]).to.eql(0);
});
});
describe('getBlankTexture', function() {
it('creates a 1x1 white texture', function() {
const texture = getBlankTexture();
expect(texture.height).to.eql(1);
expect(texture.width).to.eql(1);
expect(texture.data[0]).to.eql(255);
expect(texture.data[1]).to.eql(255);
expect(texture.data[2]).to.eql(255);
expect(texture.data[3]).to.eql(255);
});
});
});

View File

@@ -0,0 +1,142 @@
import Feature from '../../../../../src/ol/Feature.js';
import Point from '../../../../../src/ol/geom/Point.js';
import LineString from '../../../../../src/ol/geom/LineString.js';
import VectorLayer from '../../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../../src/ol/source/Vector.js';
import WebGLPointsLayerRenderer from '../../../../../src/ol/renderer/webgl/PointsLayer';
import {get as getProjection} from '../../../../../src/ol/proj';
import Polygon from '../../../../../src/ol/geom/Polygon';
import ViewHint from '../../../../../src/ol/ViewHint';
describe('ol.renderer.webgl.PointsLayer', function() {
describe('constructor', function() {
let target;
beforeEach(function() {
target = document.createElement('div');
target.style.width = '256px';
target.style.height = '256px';
document.body.appendChild(target);
});
afterEach(function() {
document.body.removeChild(target);
});
it('creates a new instance', function() {
const layer = new VectorLayer({
source: new VectorSource()
});
const renderer = new WebGLPointsLayerRenderer(layer);
expect(renderer).to.be.a(WebGLPointsLayerRenderer);
});
});
describe('#prepareFrame', function() {
let layer, renderer, frameState;
beforeEach(function() {
layer = new VectorLayer({
source: new VectorSource()
});
renderer = new WebGLPointsLayerRenderer(layer);
const projection = getProjection('EPSG:3857');
frameState = {
skippedFeatureUids: {},
viewHints: [],
viewState: {
projection: projection,
resolution: 1,
rotation: 0,
center: [10, 10]
},
size: [256, 256],
extent: [-100, -100, 100, 100]
};
});
it('calls WebGlHelper#prepareDraw', function() {
const spy = sinon.spy(renderer.helper_, 'prepareDraw');
renderer.prepareFrame(frameState);
expect(spy.called).to.be(true);
});
it('fills up a buffer with 2 triangles per point', function() {
layer.getSource().addFeature(new Feature({
geometry: new Point([10, 20])
}));
renderer.prepareFrame(frameState);
const attributePerVertex = 12;
expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex);
expect(renderer.indicesBuffer_.getArray().length).to.eql(6);
});
it('ignores geometries other than points', function() {
layer.getSource().addFeature(new Feature({
geometry: new LineString([[10, 20], [30, 20]])
}));
layer.getSource().addFeature(new Feature({
geometry: new Polygon([[10, 20], [30, 20], [30, 10], [10, 20]])
}));
renderer.prepareFrame(frameState);
expect(renderer.verticesBuffer_.getArray().length).to.eql(0);
expect(renderer.indicesBuffer_.getArray().length).to.eql(0);
});
it('clears the buffers when the features are gone', function() {
const source = layer.getSource();
source.addFeature(new Feature({
geometry: new Point([10, 20])
}));
source.removeFeature(source.getFeatures()[0]);
source.addFeature(new Feature({
geometry: new Point([10, 20])
}));
renderer.prepareFrame(frameState);
const attributePerVertex = 12;
expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex);
expect(renderer.indicesBuffer_.getArray().length).to.eql(6);
});
it('rebuilds the buffers only when not interacting or animating', function() {
const spy = sinon.spy(renderer, 'rebuildBuffers_');
frameState.viewHints[ViewHint.INTERACTING] = 1;
frameState.viewHints[ViewHint.ANIMATING] = 0;
renderer.prepareFrame(frameState);
expect(spy.called).to.be(false);
frameState.viewHints[ViewHint.INTERACTING] = 0;
frameState.viewHints[ViewHint.ANIMATING] = 1;
renderer.prepareFrame(frameState);
expect(spy.called).to.be(false);
frameState.viewHints[ViewHint.INTERACTING] = 0;
frameState.viewHints[ViewHint.ANIMATING] = 0;
renderer.prepareFrame(frameState);
expect(spy.called).to.be(true);
});
it('rebuilds the buffers only when the frame extent changed', function() {
const spy = sinon.spy(renderer, 'rebuildBuffers_');
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(1);
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(1);
frameState.extent = [10, 20, 30, 40];
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(2);
});
});
});

View File

@@ -100,10 +100,50 @@ describe('ol.View', function() {
describe('with no options', function() { describe('with no options', function() {
it('gives a correct center constraint function', function() { it('gives a correct center constraint function', function() {
const options = {}; const options = {};
const size = [512, 256];
const resolution = 1e5;
const fn = createCenterConstraint(options); const fn = createCenterConstraint(options);
expect(fn([0, 0])).to.eql([0, 0]); expect(fn([0, 0], resolution, size)).to.eql([0, 0]);
expect(fn(undefined)).to.eql(undefined); expect(fn([42, -100], resolution, size)).to.eql([42, -100]);
expect(fn([42, -100])).to.eql([42, -100]); });
});
describe('panning off the edge of the world', function() {
it('disallows going north off the world', function() {
const options = {
projection: 'EPSG:4326'
};
const size = [360, 180];
const resolution = 0.5;
const fn = createCenterConstraint(options);
expect(fn([0, 0], resolution, size)).to.eql([0, 0]);
expect(fn([0, 60], resolution, size)).to.eql([0, 45]);
expect(fn([180, 60], resolution, size)).to.eql([180, 45]);
expect(fn([-180, 60], resolution, size)).to.eql([-180, 45]);
});
it('disallows going south off the world', function() {
const options = {
projection: 'EPSG:4326'
};
const size = [360, 180];
const resolution = 0.5;
const fn = createCenterConstraint(options);
expect(fn([0, 0], resolution, size)).to.eql([0, 0]);
expect(fn([0, -60], resolution, size)).to.eql([0, -45]);
expect(fn([180, -60], resolution, size)).to.eql([180, -45]);
expect(fn([-180, -60], resolution, size)).to.eql([-180, -45]);
});
});
describe('with multiWorld: true', function() {
it('gives a correct center constraint function', function() {
const options = {multiWorld: true};
const size = [512, 256];
const resolution = 1e5;
const fn = createCenterConstraint(options);
expect(fn([0, 0], resolution, size)).to.eql([0, 0]);
expect(fn([42, -100], resolution, size)).to.eql([42, -100]);
}); });
}); });

View File

@@ -1,4 +1,9 @@
import WebGLHelper from '../../../../src/ol/webgl/Helper'; import WebGLHelper from '../../../../src/ol/webgl/Helper';
import {
create as createTransform,
rotate as rotateTransform,
scale as scaleTransform, translate as translateTransform
} from '../../../../src/ol/transform';
const VERTEX_SHADER = ` const VERTEX_SHADER = `
@@ -95,7 +100,8 @@ describe('ol.webgl.WebGLHelper', function() {
uniforms: { uniforms: {
u_test1: 42, u_test1: 42,
u_test2: [1, 3], u_test2: [1, 3],
u_test3: document.createElement('canvas') u_test3: document.createElement('canvas'),
u_test4: createTransform()
} }
}); });
h.useProgram(h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER)); h.useProgram(h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER));
@@ -116,13 +122,15 @@ describe('ol.webgl.WebGLHelper', function() {
}); });
it('has processed uniforms', function() { it('has processed uniforms', function() {
expect(h.uniforms_.length).to.eql(3); expect(h.uniforms_.length).to.eql(4);
expect(h.uniforms_[0].name).to.eql('u_test1'); expect(h.uniforms_[0].name).to.eql('u_test1');
expect(h.uniforms_[1].name).to.eql('u_test2'); expect(h.uniforms_[1].name).to.eql('u_test2');
expect(h.uniforms_[2].name).to.eql('u_test3'); expect(h.uniforms_[2].name).to.eql('u_test3');
expect(h.uniforms_[3].name).to.eql('u_test4');
expect(h.uniforms_[0].location).to.not.eql(-1); expect(h.uniforms_[0].location).to.not.eql(-1);
expect(h.uniforms_[1].location).to.not.eql(-1); expect(h.uniforms_[1].location).to.not.eql(-1);
expect(h.uniforms_[2].location).to.not.eql(-1); expect(h.uniforms_[2].location).to.not.eql(-1);
expect(h.uniforms_[3].location).to.not.eql(-1);
expect(h.uniforms_[2].texture).to.not.eql(undefined); expect(h.uniforms_[2].texture).to.not.eql(undefined);
}); });
}); });
@@ -181,5 +189,33 @@ describe('ol.webgl.WebGLHelper', function() {
}); });
}); });
describe('#makeProjectionTransform', function() {
let h;
let frameState;
beforeEach(function() {
h = new WebGLHelper();
frameState = {
size: [100, 150],
viewState: {
rotation: 0.4,
resolution: 2,
center: [10, 20]
}
};
});
it('gives out the correct transform', function() {
const scaleX = 2 / frameState.size[0] / frameState.viewState.resolution;
const scaleY = 2 / frameState.size[1] / frameState.viewState.resolution;
const given = createTransform();
const expected = createTransform();
scaleTransform(expected, scaleX, scaleY);
rotateTransform(expected, -frameState.viewState.rotation);
translateTransform(expected, -frameState.viewState.center[0], -frameState.viewState.center[1]);
expect(h.makeProjectionTransform(frameState, given)).to.eql(expected);
});
});
}); });
}); });

View File

@@ -0,0 +1,32 @@
import {create} from '../../../../src/ol/worker/version.js';
import {VERSION} from '../../../../src/ol/util.js';
describe('ol/worker/version', function() {
let worker;
beforeEach(function() {
worker = create();
});
afterEach(function() {
if (worker) {
worker.terminate();
}
worker = null;
});
describe('messaging', function() {
it('responds with the version', function(done) {
worker.addEventListener('error', done);
worker.addEventListener('message', function(event) {
expect(event.data).to.equal('version: ' + VERSION);
done();
});
worker.postMessage('test message');
});
});
});

View File

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