/**
* @module meteoJS/synview/resource
*/
import VectorLayer from 'ol/layer/Vector';
import { unByKey } from 'ol/Observable';
import addEventFunctions from '../Events.js';
/**
* Options for the constructor.
*
* @typedef {Object} module:meteoJS/synview/resource~options
* @param {undefined|String} url - URL to resource.
* @param {undefined|Date} datetime
* Datetime for this resource, undefined if resource have no datetime.
* @param {undefined|String} mimetype - MIME-Type of the resource.
* @param {undefined|Integer} reloadTime
* After this time period the resource will be reloaded. Undefined for no
* reload. (in seconds)
* @param {undefined|String} className - Type's classname.
* @param {undefined|boolean} [imageSmoothingEnabled=undefined]
* Disable image smoothing to draw sharp edges in image layers.
* Undefined uses the default (true).
* @param {Object} ol - Options for openlayers.
* @param {Object|external:ol/source/Source~Source|undefined} ol.source
* Options for openlayers source object or OL source object already.
* @param {Object.<string,Function>|undefined} ol.events
* Function to listen to module:ol/render/Event~RenderEvent.
* @param {undefined|external:ol/style/Style~Style|external:ol/style/Style~Style|external:ol/style/Style~StyleFunction} [ol.style]
* Style for features. If this is a module:ol/style/Style~StyleFunction,
* then "this" will be bound to this module:meteoJS/synview/resource~Resource.
*/
/**
* Object representing a resource.
*/
export class Resource {
/**
* @param {module:meteoJS/synview/resource~options} options - Options.
*/
constructor({
url = undefined,
datetime = undefined,
mimetype = undefined,
reloadTime = undefined,
className = undefined,
imageSmoothingEnabled = undefined,
ol = {}
} = {}) {
/**
* @type {Object}
* @private
*/
this.options = {
url,
datetime,
mimetype,
reloadTime,
className,
ol
};
this._normalizeOLOptions(this.options.ol);
/**
* @type {external:ol.layer.Layer|undefined}
* @private
*/
this.layer = undefined;
/**
* @type {ol.layer.Group|L.layerGroup|undefined}
* @private
*/
this.layerGroup = undefined;
/**
* @type {number|undefined}
* @private
*/
this.reloadTimerId = undefined;
/**
* @type {boolean}
* @private
*/
this.visible = false;
/**
* @type {number|undefined}
* @private
*/
this.zIndex = undefined;
/**
* @type {number}
* @private
*/
this.opacity = 1.0;
/**
* @type {boolean}
* @private
*/
this._imageSmoothing = imageSmoothingEnabled;
}
/**
* Returns an ID for this resource. Should change, if content of resource
* changes.
*
* @return {mixed} Id.
*/
getId() {
return this.getUrl();
}
/**
* Returns URL to the resource. Undefined if unknown.
*
* @return {string|undefined} URL.
*/
getUrl() {
return this.options.url;
}
/**
* Returns the datetime of the resource.
*
* @return {Date|undefined} Date.
*/
getDatetime() {
return this.options.datetime;
}
/**
* Returns MIME-Type of the resource.
*
* @return {string} MIME-Type.
*/
getMIMEType() {
return (this.options.mimetype === undefined) ?
'application/octet-stream' : this.options.mimetype;
}
/**
* Returns the current reload time.
*
* @return {undefined|integer} Reload time period.
*/
getReloadTime() {
return this.options.reloadTime;
}
/**
* Sets the reload time.
*
* @param {undefined|integer} reloadTime Reload time period.
* @return {module:meteoJS/synview/resource.Resource} This.
*/
setReloadTime(reloadTime) {
this.options.reloadTime = reloadTime;
this._reload(); // starts or stops frequent reload
return this;
}
/**
* Returns the visibility of the resource layer.
*
* @return {boolean} Visible.
*/
getVisible() {
return this.visible;
}
/**
* Sets the visibility of the resource layer.
*
* @param {boolean} visible Visible.
* @return {module:meteoJS/synview/resource.Resource} This.
*/
setVisible(visible) {
this.visible = visible;
if (this.layer !== undefined) {
// OpenLayers
if ('setVisible' in this.layer)
this.layer.setVisible(visible);
// Leaflet
else {
if (this.visible)
this.layer.addTo(this.layerGroup);
else
this.layerGroup.removeLayer(this.layer);
}
}
return this;
}
/**
* Returns the z-Index of the resource layer.
*
* @return {number|undefined} z-Index.
*/
getZIndex() {
return this.zIndex;
}
/**
* Sets the z-Index of the resource layer.
*
* @param {number|undefined} zIndex z-Index.
* @return {module:meteoJS/synview/resource.Resource} This.
*/
setZIndex(zIndex) {
this.zIndex = zIndex;
if (this.layer !== undefined)
this.layer.setZIndex(zIndex);
return this;
}
/**
* Returns opacity of the resource layer.
*
* @return {number} Opacity.
*/
getOpacity() {
return this.opacity;
}
/**
* Sets opacity of the resource layer.
*
* @param {number} opacity Opacity.
* @return {module:meteoJS/synview/resource.Resource} This.
*/
setOpacity(opacity) {
this.opacity = opacity;
if (this.layer !== undefined)
this.layer.setOpacity(opacity);
return this;
}
/**
* Classname.
*
* @type undefined|String
*/
get className() {
return this.options.className;
}
set className(className) {
this.options.className = className;
}
/**
* imageSmoothingEnabled.
*
* @type undefined|boolean
*/
get imageSmoothingEnabled() {
return this._imageSmoothing;
}
set imageSmoothingEnabled(imageSmoothing) {
this._imageSmoothing = imageSmoothing;
}
/**
* Returns the layer group of the resource layer.
*
* @return {external:ol.layer.group|external:L.layerGroup|undefined} Layer group.
*/
getLayerGroup() {
return this.layerGroup;
}
/**
* Sets the layer group and adds the resource layer to this group.
* If undefined is passed, the resource layer will be deleted and removed for
* any layer group.
*
* @param {external:ol.layer.group|external:L.layerGroup|undefined} layerGroup Layer group.
* @return {module:meteoJS/synview/resource.Resource} This.
*/
setLayerGroup(layerGroup) {
if (this.layerGroup !== undefined &&
this.layer !== undefined) {
// OpenLayers
if ('remove' in this.layerGroup.getLayers())
this.layerGroup.getLayers().remove(this.layer);
// Leaflet
else
this.layerGroup.removeLayer(this.layer);
}
if (layerGroup === undefined)
this.layer = undefined;
this.layerGroup = layerGroup;
if (this.layerGroup !== undefined) {
// Leaflet
if ('addLayer' in this.layerGroup) {
var layer = this.getLLLayer();
if (this.getVisible())
this.layerGroup.addLayer(layer);
}
// OpenLayers
else
this.layerGroup.getLayers().push(this.getOLLayer());
}
this.setReloadTime(this.getReloadTime()); // Trigger reload interval
return this;
}
/**
* Returns layer for openlayers of this resource.
*
* @return {external:ol.layer.Layer} openlayers layer.
*/
getOLLayer() {
if (this.layer !== undefined)
return this.layer;
this.layer = this._makeOLLayer();
return this.layer;
}
/**
* Returns openlayers layer of this resource. Must be overwritten by child
* classes.
*
* @protected
* @return {external:ol.layer.Layer} openlayers layer.
*/
makeOLLayer() {
// Dies on instantiation of ol.layer.Layer, so use ol.layer.Vector
return new VectorLayer({
className: this.className
});
}
/**
* Returns a ready to use OpenLayers layer.
*
* @private
* @return {external:ol.layer.Layer} openlayers layer.
*/
_makeOLLayer() {
let layer = this.makeOLLayer();
layer.setVisible(this.visible);
layer.setZIndex(this.zIndex);
layer.setOpacity(this.opacity);
if ('events' in this.options.ol &&
this.options.ol.events !== undefined)
['prerender', 'postrender'].forEach(eventName => {
if (eventName in this.options.ol.events &&
this.options.ol.events[eventName] !== undefined)
layer.on(eventName, event => {
this.options.ol.events[eventName].call(this, event, layer);
});
});
if (!this._imageSmoothing) {
const source = layer.getSource();
if (source !== null &&
'contextOptions_' in source)
source.contextOptions_ = {
imageSmoothingEnabled: false,
msImageSmoothingEnabled: false
};
}
return layer;
}
/**
* Returns layer for Leaflet of this resource.
*
* @return {external:L.layer} Leaflet layer.
*/
getLLLayer() {
if (this.layer !== undefined)
return this.layer;
this.layer = this._makeLLLayer();
return this.layer;
}
/**
* Returns Leaflet layer of this resource. Must be overwritten by child
* classes.
*
* @protected
* @return {external:L.Layer} Leaflet layer.
*/
makeLLLayer() {
// Dies on instantiation of ol.layer.Layer, so use ol.layer.Vector
return L.Layer();
}
/**
* Preload resource. By default, openlayers loads the resource as soon as
* the resource gets visible.
*/
preload() {}
/**
* Returns a ready to use Leaflet layer.
*
* @private
* @return {external:L.Layer} Leaflet layer.
*/
_makeLLLayer() {
return this.makeLLLayer();
}
/**
* Reload source.
*
* @private
* @return {module:meteoJS/synview/resource.Resource} This.
*/
_reload() {
// Stop possible earlier reload
if (this.reloadTimerId !== undefined) {
clearTimeout(this.reloadTimerId);
this.reloadTimerId = undefined;
}
// No frequent reload
if (this.options.reloadTime === undefined)
return;
// Reload could only be handled, if layerGroup is defined
if (this.layerGroup === undefined)
return;
var reloadFunction = (function () {
this.reloadTimerId = undefined;
if (this.layerGroup === undefined)
return;
var layer = this._makeOLLayer();
// Hackish reload of sources, it is not handled properly by OpenLayers.
// 1. Non-tile sources, they have a 'getUrl' method.
if ('getUrl' in layer.getSource()) {
var layerGroup = this.layerGroup;
// event triggered, even if source is cached.
var key = layer.getSource().on('change', (function () {
if (layer.getSource().getState() == 'ready' ||
layer.getSource().getState() == 'error') {
// Execute code once, once the data is loaded.
unByKey(key);
if (layer.getSource().getState() == 'ready' &&
this.layerGroup !== undefined) {
layer.setVisible(this.layer.getVisible());
layer.setOpacity(this.layer.getOpacity());
layer.setZIndex(this.layer.getZIndex());
this.layerGroup.getLayers().remove(this.layer);
this.layer = layer;
}
else if (this.layerGroup !== undefined)
this.layerGroup.getLayers().remove(layer);
else
layerGroup.getLayers().remove(layer);
if (this.reloadTimerId === undefined &&
this.options.reloadTime !== undefined &&
this.layerGroup !== undefined)
this.reloadTimerId =
setTimeout(reloadFunction, this.options.reloadTime * 1000);
}
}).bind(this));
this.layerGroup.getLayers().push(layer);
layer.setVisible(true); // Force load of data by make the layer visible.
}
else {
/* Tile sources in OpenLayers doesn't support a request to check, if all
* tiles are loaded, because cached tiles doesn't generate any event.
* Uncached tiles fire tileloadstart/end/error events.
* So we wait a second and exchange then the old with the new layer. If
* the reload of the data is smaller of one second, this suppresses that
* neither the old layer nor the new data is visible. */
this.layerGroup.getLayers().push(layer);
layer.setVisible(true);
setTimeout((function () {
this.layer = layer;
if (this.reloadTimerId === undefined &&
this.options.reloadTime !== undefined)
this.reloadTimerId =
setTimeout(reloadFunction, this.options.reloadTime * 1000);
}).bind(this), 1000);
}
}).bind(this);
this.reloadTimerId =
setTimeout(reloadFunction, this.options.reloadTime * 1000);
return this;
}
/**
* Normalizes this.options.ol.
*
* @private
* @param {Object|external:ol/source/Source~Source|undefined} source
* @param {Object.<string,Function>|undefined} events
* @param {external:ol/style/Style~Style|external:ol/style/Style~StyleLike|external:ol/style/Style~StyleFunction|undefined} [style]
*/
_normalizeOLOptions({
source = {},
events = undefined,
style = undefined
}) {
this.options.ol = {
source,
events,
style
};
}
}
addEventFunctions(Resource.prototype);
export default Resource;