diff --git a/examples/mapbox-layer.js b/examples/mapbox-layer.js index 042c158979..f9dee5440f 100644 --- a/examples/mapbox-layer.js +++ b/examples/mapbox-layer.js @@ -1,132 +1,12 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import Layer from '../src/ol/layer/Layer'; -import {assign} from '../src/ol/obj'; import {toLonLat} from '../src/ol/proj'; -import SourceState from '../src/ol/source/State'; import {Stroke, Style} from '../src/ol/style.js'; import VectorLayer from '../src/ol/layer/Vector.js'; import VectorSource from '../src/ol/source/Vector.js'; import GeoJSON from '../src/ol/format/GeoJSON.js'; -class Mapbox extends Layer { - - /** - * @param {import('../src/ol/layer/Layer').Options} options Layer options. - */ - constructor(options) { - const baseOptions = assign({}, options); - super(baseOptions); - - this.baseOptions = baseOptions; - - /** - * @private - * @type boolean - */ - this.loaded = false; - - this.initMap(); - } - - initMap() { - const map = this.map_; - const view = map.getView(); - const center = toLonLat(view.getCenter(), view.getProjection()); - - const options = assign(this.baseOptions, { - attributionControl: false, - boxZoom: false, - center, - container: map.getTargetElement(), - doubleClickZoom: false, - dragPan: false, - dragRotate: false, - interactive: false, - keyboard: false, - pitchWithRotate: false, - scrollZoom: false, - touchZoomRotate: false, - zoom: view.getZoom() - 1 - }); - - this.mbmap = new mapboxgl.Map(options); - - this.mbmap.on('load', function() { - this.loaded = true; - this.map_.render(); - this.mbmap.getContainer().querySelector('.mapboxgl-control-container').remove(); - }.bind(this)); - - } - - /** - * - * @inheritDoc - */ - render(frameState) { - const map = this.map_; - const view = map.getView(); - - // adjust view parameters in mapbox - const rotation = frameState.viewState.rotation; - if (rotation) { - this.mbmap.rotateTo(-rotation * 180 / Math.PI, { - animate: false - }); - } - const center = toLonLat(view.getCenter(), view.getProjection()); - const zoom = view.getZoom() - 1; - this.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 (this.mbmap._frame) { - this.mbmap._frame.cancel(); - this.mbmap._frame = null; - } - this.mbmap._render(); - - return this.mbmap.getCanvas(); - } - - setVisible(visible) { - super.setVisible(visible); - - const canvas = this.mbmap.getCanvas(); - canvas.style.display = visible ? 'block' : 'none'; - } - - setOpacity(opacity) { - super.setOpacity(opacity); - const canvas = this.mbmap.getCanvas(); - canvas.style.opacity = opacity; - } - - setZIndex(zindex) { - super.setZIndex(zindex); - const canvas = this.mbmap.getCanvas(); - canvas.style.zIndex = zindex; - } - - /** - * @inheritDoc - */ - getSourceState() { - return this.loaded ? SourceState.READY : SourceState.UNDEFINED; - } - - setMap(map) { - this.map_ = map; - } - -} - const style = new Style({ stroke: new Stroke({ color: '#319FD3', @@ -154,11 +34,71 @@ const map = new Map({ }) }); + +// init Mapbox object + +const view = map.getView(); +const center = toLonLat(view.getCenter(), view.getProjection()); const key = 'ER67WIiPdCQvhgsUjoWK'; -const mbLayer = new Mapbox({ - map: map, + +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({ container: map.getTarget(), - style: 'https://maps.tilehosting.com/styles/bright/style.json?key=' + key + 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); diff --git a/src/ol/layer/Layer.js b/src/ol/layer/Layer.js index 1c9f916c47..775a0cd3d1 100644 --- a/src/ol/layer/Layer.js +++ b/src/ol/layer/Layer.js @@ -10,6 +10,10 @@ import {assign} from '../obj.js'; import RenderEventType from '../render/EventType.js'; import SourceState from '../source/State.js'; +/** + * @typedef {function(import("../PluggableMap.js").FrameState):HTMLElement} RenderFunction + */ + /** * @typedef {Object} Options @@ -29,6 +33,8 @@ import SourceState from '../source/State.js'; * the source can be set by calling {@link module:ol/layer/Layer#setSource layer.setSource(source)} after * construction. * @property {import("../PluggableMap.js").default} [map] Map. + * @property {RenderFunction} [render] Render function. Takes the frame state as input and is expected to return an + * HTML element. Will overwrite the default rendering for the layer. */ @@ -47,8 +53,10 @@ import SourceState from '../source/State.js'; /** * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. + * Base class from which all layer types are derived. This should only be instantiated + * in the case where a custom layer is be added to the map with a custom `render` function. + * Such a function can be specified in the `options` object, and is expected to return an HTML element. + * * A visual representation of raster or vector map data. * Layers group together those properties that pertain to how the data is to be * displayed, irrespective of the source of that data. @@ -64,6 +72,7 @@ import SourceState from '../source/State.js'; * @fires import("../render/Event.js").RenderEvent#postrender * * @template {import("../source/Source.js").default} SourceType + * @api */ class Layer extends BaseLayer { /** @@ -100,6 +109,11 @@ class Layer extends BaseLayer { */ this.renderer_ = null; + // Overwrite default render method with a custom one + if (options.render) { + this.render = options.render; + } + if (options.map) { this.setMap(options.map); } diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index 2d0908c1fa..9b4ce2a1e7 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -85,7 +85,8 @@ class CompositeMapRenderer extends MapRenderer { this.children_.length = 0; for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) { const layerState = layerStatesArray[i]; - if (!visibleAtResolution(layerState, viewResolution) || layerState.sourceState != SourceState.READY) { + if (!visibleAtResolution(layerState, viewResolution) || + (layerState.sourceState != SourceState.READY && layerState.sourceState != SourceState.UNDEFINED)) { continue; } diff --git a/test/spec/ol/layer/layer.test.js b/test/spec/ol/layer/layer.test.js index dba518a47a..010a289687 100644 --- a/test/spec/ol/layer/layer.test.js +++ b/test/spec/ol/layer/layer.test.js @@ -94,6 +94,16 @@ describe('ol.layer.Layer', function() { layer.dispose(); }); + it('accepts a custom render function', function() { + let called = false; + const layer = new Layer({ + render: function() { + called = true; + } + }); + layer.render(); + expect(called).to.eql(true); + }); }); describe('visibleAtResolution', function() {