kmap-ui
Version:
A components of zmap base on vue2.X
686 lines (638 loc) • 21.4 kB
JavaScript
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
import ol_ext_inherits from '../util/ext'
import ol_source_Vector from 'ol/source/Vector'
import ol_control_Control from 'ol/control/Control'
import ol_ext_element from '../util/element'
/** Timeline control
*
* @constructor
* @extends {ol.control.Control}
* @fires select
* @fires scroll
* @fires collapse
* @param {Object=} options Control options.
* @param {String} options.className class of the control
* @param {Array<ol.Feature>} options.features Features to show in the timeline
* @param {ol.SourceImageOptions.vector} options.source class of the control
* @param {Number} options.interval time interval length in ms or a text with a format d, h, mn, s (31 days = '31d'), default none
* @param {String} options.maxWidth width of the time line in px, default 2000px
* @param {String} options.minDate minimum date
* @param {String} options.maxDate maximum date
* @param {Number} options.minZoom Minimum zoom for the line, default .2
* @param {Number} options.maxZoom Maximum zoom for the line, default 4
* @param {boolean} options.zoomButton Are zoom buttons avaliable, default false
* @param {function} options.getHTML a function that takes a feature and returns the html to display
* @param {function} options.getFeatureDate a function that takes a feature and returns its date, default the date propertie
* @param {function} options.endFeatureDate a function that takes a feature and returns its end date, default no end date
* @param {String} options.graduation day|month to show month or day graduation, default show only years
* @param {String} options.scrollTimeout Time in milliseconds to get a scroll event, default 15ms
*/
var ol_control_Timeline = function(options) {
var element = ol_ext_element.create('DIV', {
className: (options.className || '') + ' ol-timeline'
+ (options.target ? '': ' ol-unselectable ol-control')
+ (options.zoomButton ? ' ol-hasbutton':'')
+ ('ontouchstart' in window ? ' ol-touch' : '')
});
// Initialize
ol_control_Control.call(this, {
element: element,
target: options.target
});
// Scroll div
this._scrollDiv = ol_ext_element.create('DIV', {
className: 'ol-scroll',
parent: this.element
});
// Add a button bar
this._buttons = ol_ext_element.create('DIV', {
className: 'ol-buttons',
parent: this.element
});
// Zoom buttons
if (options.zoomButton) {
// Zoom in
this.addButton({
className: 'ol-zoom-in',
handleClick: function(){
var zoom = this.get('zoom');
if (zoom>=1) {
zoom++;
} else {
zoom = Math.min(1, zoom + 0.1);
}
zoom = Math.round(zoom*100)/100;
this.refresh(zoom);
}.bind(this)
});
// Zoom out
this.addButton({
className: 'ol-zoom-out',
handleClick: function(){
var zoom = this.get('zoom');
if (zoom>1) {
zoom--;
} else {
zoom -= 0.1;
}
zoom = Math.round(zoom*100)/100;
this.refresh(zoom);
}.bind(this)
});
}
// Draw center date
this._intervalDiv = ol_ext_element.create('DIV', {
className: 'ol-center-date',
parent: this.element
});
// Remove selection
this.element.addEventListener('mouseover', function(){
if (this._select) this._select.elt.classList.remove('ol-select');
}.bind(this));
// Trigger scroll event
var scrollListener = null;
this._scrollDiv.addEventListener('scroll', function() {
this._setScrollLeft();
if (scrollListener) {
clearTimeout(scrollListener);
scrollListener = null;
}
scrollListener = setTimeout(function() {
this.dispatchEvent({
type: 'scroll',
date: this.getDate(),
dateStart: this.getDate('start'),
dateEnd: this.getDate('end')
});
}.bind(this), options.scrollTimeout || 15);
}.bind(this));
// Magic to give "live" scroll events on touch devices
this._scrollDiv.addEventListener('gesturechange', function() {});
// Scroll timeline
ol_ext_element.scrollDiv(this._scrollDiv, {
onmove: function(b) {
// Prevent selection on moving
this._moving = b;
}.bind(this)
});
this._tline = [];
// Parameters
this._scrollLeft = 0;
this.set('maxWidth', options.maxWidth || 2000);
this.set('minDate', options.minDate || Infinity);
this.set('maxDate', options.maxDate || -Infinity);
this.set('graduation', options.graduation);
this.set('minZoom', options.minZoom || .2);
this.set('maxZoom', options.maxZoom || 4);
this.setInterval(options.interval);
if (options.getHTML) this._getHTML = options.getHTML;
if (options.getFeatureDate) this._getFeatureDate = options.getFeatureDate;
if (options.endFeatureDate) this._endFeatureDate = options.endFeatureDate;
// Feature source
this.setFeatures(options.features || options.source, options.zoom);
};
ol_ext_inherits(ol_control_Timeline, ol_control_Control);
/**
* Set the map instance the control is associated with
* and add interaction attached to it to this map.
* @param {_ol_Map_} map The map instance.
*/
ol_control_Timeline.prototype.setMap = function(map) {
ol_control_Control.prototype.setMap.call(this, map);
this.refresh(this.get('zoom')||1, true);
};
/** Add a button on the timeline
* @param {*} button
* @param {string} button.className
* @param {title} button.className
* @param {Element|string} button.html Content of the element
* @param {function} button.click a function called when the button is clicked
*/
ol_control_Timeline.prototype.addButton = function(button) {
this.element.classList.add('ol-hasbutton');
ol_ext_element.create('BUTTON', {
className: button.className || undefined,
title: button.title,
html : button.html,
click: button.handleClick,
parent: this._buttons
})
};
/** Set an interval
* @param {number|string} length the interval length in ms or a farmatted text ie. end with y, 1d, h, mn, s (31 days = '31d'), default none
*/
ol_control_Timeline.prototype.setInterval = function(length) {
if (typeof(length)==='string') {
if (/s$/.test(length)) {
length = parseFloat(length) * 1000;
} else if (/mn$/.test(length)) {
length = parseFloat(length) * 1000 * 60;
} else if (/h$/.test(length)) {
length = parseFloat(length) * 1000 * 3600;
} else if (/d$/.test(length)) {
length = parseFloat(length) * 1000 * 3600 * 24;
} else if (/y$/.test(length)) {
length = parseFloat(length) * 1000 * 3600 * 24 * 365;
} else {
length = 0;
}
}
this.set('interval', length || 0);
if (length) this.element.classList.add('ol-interval');
else this.element.classList.remove('ol-interval');
this.refresh(this.get('zoom'));
}
/** Default html to show in the line
* @param {ol.Feature} feature
* @return {DOMElement|string}
* @private
*/
ol_control_Timeline.prototype._getHTML = function(feature) {
return feature.get('name') || '';
};
/** Default function to get the date of a feature, returns the date attribute
* @param {ol.Feature} feature
* @return {Data|string}
* @private
*/
ol_control_Timeline.prototype._getFeatureDate = function(feature) {
return (feature && feature.get) ? feature.get('date') : null;
};
/** Default function to get the end date of a feature, return undefined
* @param {ol.Feature} feature
* @return {Data|string}
* @private
*/
ol_control_Timeline.prototype._endFeatureDate = function(/* feature */) {
return undefined;
};
/** Is the line collapsed
* @return {boolean}
*/
ol_control_Timeline.prototype.isCollapsed = function() {
return this.element.classList.contains('ol-collapsed');
};
/** Collapse the line
* @param {boolean} b
*/
ol_control_Timeline.prototype.collapse = function(b) {
if (b) this.element.classList.add('ol-collapsed');
else this.element.classList.remove('ol-collapsed');
this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() });
};
/** Collapse the line
*/
ol_control_Timeline.prototype.toggle = function() {
this.element.classList.toggle('ol-collapsed');
this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() });
};
/** Set the features to display in the timeline
* @param {Array<ol.Features>|ol.source.Vector} features An array of features or a vector source
* @param {number} zoom zoom to draw the line default 1
*/
ol_control_Timeline.prototype.setFeatures = function(features, zoom) {
this._features = this._source = null;
if (features instanceof ol_source_Vector) this._source = features;
else if (features instanceof Array) this._features = features;
else this._features = [];
this.refresh(zoom);
};
/**
* Get features
* @return {Array<ol.Feature>}
*/
ol_control_Timeline.prototype.getFeatures = function() {
return this._features || this._source.getFeatures();
}
/**
* Refresh the timeline with new data
* @param {Number} zoom Zoom factor from 0.25 to 10, default 1
*/
ol_control_Timeline.prototype.refresh = function(zoom, first) {
if (!this.getMap()) return;
if (!zoom) zoom = this.get('zoom');
zoom = Math.min(this.get('maxZoom'), Math.max(this.get('minZoom'), zoom || 1));
this.set('zoom', zoom);
this._scrollDiv.innerHTML = '';
var features = this.getFeatures();
var d, d2;
// Get features sorted by date
var tline = this._tline = [];
features.forEach(function(f) {
if (d = this._getFeatureDate(f)) {
if (!(d instanceof Date)) {
d = new Date(d)
}
if (this._endFeatureDate) {
d2 = this._endFeatureDate(f);
if (!(d2 instanceof Date)) {
d2 = new Date(d2)
}
}
if (!isNaN(d)) {
tline.push({
date: d,
end: isNaN(d2) ? null : d2,
feature: f
});
}
}
}.bind(this));
tline.sort(function(a,b) {
return (a.date < b.date ? -1 : (a.date===b.date ? 0: 1))
});
// Draw
var div = ol_ext_element.create('DIV', {
parent: this._scrollDiv
});
// Calculate width
var min = this._minDate = Math.min(this.get('minDate'), tline.length ? tline[0].date : Infinity);
var max = this._maxDate = Math.max(this.get('maxDate'), tline.length ? tline[tline.length-1].date : -Infinity);
if (!isFinite(min)) this._minDate = min = new Date();
if (!isFinite(max)) this._maxDate = max = new Date();
var delta = (max-min);
var maxWidth = this.get('maxWidth');
var scale = this._scale = (delta > maxWidth ? maxWidth/delta : 1) * zoom;
// Leave 10px on right
min = this._minDate = this._minDate - 10/scale;
delta = (max-min) * scale;
ol_ext_element.setStyle(div, {
width: delta,
maxWidth: 'unset'
});
// Draw time's bar
this._drawTime(div, min, max, scale);
// Set interval
if (this.get('interval')) {
ol_ext_element.setStyle (this._intervalDiv, { width: this.get('interval') * scale });
} else {
ol_ext_element.setStyle (this._intervalDiv, { width: '' });
}
// Draw features
var line = [];
var lineHeight = ol_ext_element.getStyle(this._scrollDiv, 'lineHeight');
// Wrapper
var fdiv = ol_ext_element.create('DIV', {
className: 'ol-features',
parent: div
});
// Add features on the line
tline.forEach(function(f) {
var d = f.date;
var t = f.elt = ol_ext_element.create('DIV', {
className: 'ol-feature',
style: {
left: Math.round((d-min)*scale),
},
html: this._getHTML(f.feature),
parent: fdiv
});
// Prevent image dragging
var img = t.querySelectorAll('img');
for (var i=0; i<img.length; i++) {
img[i].ondragstart = function(){ return false; };
}
// Calculate image width
if (f.end) {
ol_ext_element.setStyle(t, {
minWidth: (f.end-d) * scale,
width: (f.end-d) * scale,
maxWidth: 'unset'
});
}
var left = ol_ext_element.getStyle(t, 'left');
// Select on click
t.addEventListener('click', function(){
if (!this._moving) {
this.dispatchEvent({type: 'select', feature: f.feature });
}
}.bind(this));
// Find first free Y position
var pos, l;
for (pos=0; l=line[pos]; pos++) {
if (left > l) {
break;
}
}
line[pos] = left + ol_ext_element.getStyle(t, 'width');
ol_ext_element.setStyle(t, { top: pos*lineHeight });
}.bind(this));
this._nbline = line.length;
if (first) this.setDate(this._minDate, { anim: false, position: 'start' });
// Dispatch scroll event
this.dispatchEvent({
type: 'scroll',
date: this.getDate(),
dateStart: this.getDate('start'),
dateEnd: this.getDate('end')
});
};
/** Get offset given a date
* @param {Date} date
* @return {number}
* @private
*/
ol_control_Timeline.prototype._getOffsetFromDate = function(date) {
return (date - this._minDate) * this._scale;
};
/** Get date given an offset
* @param {Date} date
* @return {number}
* @private
*/
ol_control_Timeline.prototype._getDateFromOffset = function(offset) {
return offset / this._scale + this._minDate;
};
/** Set the current position
* @param {number} scrollLeft current position (undefined when scrolling)
* @returns {number}
* @private
*/
ol_control_Timeline.prototype._setScrollLeft = function(scrollLeft) {
this._scrollLeft = scrollLeft;
if (scrollLeft !== undefined) {
this._scrollDiv.scrollLeft = scrollLeft;
}
};
/** Get the current position
* @returns {number}
* @private
*/
ol_control_Timeline.prototype._getScrollLeft = function() {
// Unset when scrolling
if (this._scrollLeft === undefined) {
return this._scrollDiv.scrollLeft;
} else {
// St by user
return this._scrollLeft;
}
};
/**
* Draw dates on line
* @private
*/
ol_control_Timeline.prototype._drawTime = function(div, min, max, scale) {
// Times div
var tdiv = ol_ext_element.create('DIV', {
className: 'ol-times',
parent: div
});
var d, dt, month, dmonth;
var dx = ol_ext_element.getStyle(tdiv, 'left');
var heigth = ol_ext_element.getStyle(tdiv, 'height');
// Year
var year = (new Date(this._minDate)).getFullYear();
dt = ((new Date(0)).setFullYear(String(year)) - new Date(0).setFullYear(String(year-1))) * scale;
var dyear = Math.round(2*heigth/dt)+1;
while(true) {
d = new Date(0).setFullYear(year);
if (d > this._maxDate) break;
ol_ext_element.create('DIV', {
className: 'ol-time ol-year',
style: {
left: this._getOffsetFromDate(d) - dx
},
html: year,
parent: tdiv
});
year += dyear;
}
// Month
if (/day|month/.test(this.get('graduation'))) {
dt = ((new Date(0,0,1)).setFullYear(String(year)) - new Date(0,0,1).setFullYear(String(year-1))) * scale;
dmonth = Math.max(1, Math.round(12 / Math.round(dt/heigth/2)));
if (dmonth < 12) {
year = (new Date(this._minDate)).getFullYear();
month = dmonth+1;
while(true) {
d = new Date(0,0,1);
d.setFullYear(year);
d.setMonth(month-1);
if (d > this._maxDate) break;
ol_ext_element.create('DIV', {
className: 'ol-time ol-month',
style: {
left: this._getOffsetFromDate(d) - dx
},
html: d.toLocaleDateString(undefined, { month: 'short'}),
parent: tdiv
});
month += dmonth;
if (month > 12) {
year++;
month = dmonth+1;
}
}
}
}
// Day
if (this.get('graduation')==='day') {
dt = (new Date(0,1,1) - new Date(0,0,1)) * scale;
var dday = Math.max(1, Math.round(31 / Math.round(dt/heigth/2)));
if (dday < 31) {
year = (new Date(this._minDate)).getFullYear();
month = 0;
var day = dday;
while(true) {
d = new Date(0,0,1);
d.setFullYear(year);
d.setMonth(month);
d.setDate(day);
if (isNaN(d)) {
month++;
if (month>12) {
month = 1;
year++;
}
day = dday;
} else {
if (d > this._maxDate) break;
ol_ext_element.create('DIV', {
className: 'ol-time ol-day',
style: {
left: this._getOffsetFromDate(d) - dx
},
html: day,
parent: tdiv
});
year = d.getFullYear();
month = d.getMonth();
day = d.getDate() + dday;
if (day+dday/2>31) {
month++;
day = dday;
}
}
}
}
}
};
/** Center timeline on a date
* @param {Date|String|ol.feature} feature a date or a feature with a date
* @param {Object} options
* @param {boolean} options.anim animate scroll
* @param {string} options.position start, end or middle, default middle
*/
ol_control_Timeline.prototype.setDate = function(feature, options) {
var date;
options = options || {};
// It's a date
if (feature instanceof Date) {
date = feature;
} else {
// Get date from Feature
if (this.getFeatures().indexOf(feature) >= 0) {
date = this._getFeatureDate(feature);
}
if (date && !(date instanceof Date)) {
date = new Date(date);
}
if (!date || isNaN(date)) {
date = new Date(String(feature));
}
}
if (!isNaN(date)) {
if (options.anim === false) this._scrollDiv.classList.add('ol-move');
var scrollLeft = this._getOffsetFromDate(date);
if (options.position==='start') {
scrollLeft += ol_ext_element.outerWidth(this._scrollDiv)/2 - ol_ext_element.getStyle(this._scrollDiv, 'marginLeft')/2;
} else if (options.position==='end') {
scrollLeft -= ol_ext_element.outerWidth(this._scrollDiv)/2 - ol_ext_element.getStyle(this._scrollDiv, 'marginLeft')/2;
}
this._setScrollLeft(scrollLeft);
if (options.anim === false) this._scrollDiv.classList.remove('ol-move');
if (feature) {
for (var i=0, f; f = this._tline[i]; i++) {
if (f.feature === feature) {
f.elt.classList.add('ol-select');
this._select = f;
} else {
f.elt.classList.remove('ol-select');
}
}
}
}
};
/** Get round date (sticked to mn, hour day or month)
* @param {Date} d
* @param {string} stick sticking option to stick date to: 'mn', 'hour', 'day', 'month', default no stick
* @return {Date}
*/
ol_control_Timeline.prototype.roundDate = function(d, stick) {
switch (stick) {
case 'mn': {
return new Date(this._roundTo(d, 60*1000));
}
case 'hour': {
return new Date(this._roundTo(d, 60*60*1000));
}
case 'day': {
return new Date(this._roundTo(d, 24*60*60*1000));
}
case 'month': {
d = new Date(this._roundTo(d, 24*60*60*1000));
if (d.getDate() > 15) {
d = new Date(d.setMonth(d.getMonth() + 1));
}
d = d.setDate(1);
return new Date(d);
}
default: return new Date(d);
}
};
/** Get the date of the center
* @param {string} position position to get 'start', 'end' or 'middle', default middle
* @param {string} stick sticking option to stick date to: 'mn', 'hour', 'day', 'month', default no stick
* @return {Date}
*/
ol_control_Timeline.prototype.getDate = function(position, stick) {
var pos;
if (!stick) stick = position;
switch (position) {
case 'start': {
if (this.get('interval')) {
pos = - ol_ext_element.getStyle(this._intervalDiv, 'width')/2 + ol_ext_element.getStyle(this._scrollDiv, 'marginLeft')/2;
} else {
pos = - ol_ext_element.outerWidth(this._scrollDiv)/2 + ol_ext_element.getStyle(this._scrollDiv, 'marginLeft')/2;
}
break;
}
case 'end': {
if (this.get('interval')) {
pos = ol_ext_element.getStyle(this._intervalDiv, 'width')/2 - ol_ext_element.getStyle(this._scrollDiv, 'marginLeft')/2;
} else {
pos = ol_ext_element.outerWidth(this._scrollDiv)/2 - ol_ext_element.getStyle(this._scrollDiv, 'marginLeft')/2;
}
break;
}
default: {
pos = 0;
break;
}
}
var d = this._getDateFromOffset(this._getScrollLeft() + pos);
d = this.roundDate(d, stick);
return new Date(d);
};
/** Round number to
* @param {number} d
* @param {number} r
* @return {number}
* @private
*/
ol_control_Timeline.prototype._roundTo = function(d, r) {
return Math.round(d/r) * r;
};
/** Get the start date of the control
* @return {Date}
*/
ol_control_Timeline.prototype.getStartDate = function() {
return new Date(this.get('minDate'));
}
/** Get the end date of the control
* @return {Date}
*/
ol_control_Timeline.prototype.getEndDate = function() {
return new Date(this.get('maxDate'));
}
export default ol_control_Timeline