kmap-ui
Version:
A components of zmap base on vue2.X
536 lines (489 loc) • 18.1 kB
JavaScript
/* Copyright (c) 2016 Jean-Marc VIGLINO,
released under the CeCILL-B license (French BSD license)
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
import ol_ext_inherits from '../util/ext'
import {getDistance as ol_sphere_getDistance} from 'ol/sphere'
import {transform as ol_proj_transform} from 'ol/proj'
import ol_control_Control from 'ol/control/Control'
import ol_Feature from 'ol/Feature'
import ol_style_Style from 'ol/style/Style'
import ol_style_Stroke from 'ol/style/Stroke'
import ol_style_Text from 'ol/style/Text'
import {ol_coordinate_dist2d} from "../geom/GeomUtils";
/**
* @classdesc OpenLayers 3 Profil Control.
* Draw a profil of a feature (with a 3D geometry)
*
* @constructor
* @extends {ol_control_Control}
* @fires over, out, show
* @param {Object=} options
* @param {string} className
* @param {ol.style.Style} style style to draw the profil
* @param {*} info keys/values for i19n
* @param {number} width
* @param {number} height
* @parma {ol.Feature} feature the feature to draw profil
*/
var ol_control_Profil = function(opt_options) {
var options = opt_options || {};
this.info = options.info || ol_control_Profil.prototype.info;
var self = this;
var element;
if (options.target) {
element = document.createElement("div");
element.classList.add(options.className || "ol-profil");
} else {
element = document.createElement("div");
element.className = ((options.className || 'ol-profil') +' ol-unselectable ol-control ol-collapsed').trim();
this.button = document.createElement("button");
this.button.setAttribute('type','button');
var click_touchstart_function = function(e) {
self.toggle();
e.preventDefault();
};
this.button.addEventListener("click", click_touchstart_function);
this.button.addEventListener("touchstart", click_touchstart_function);
element.appendChild(this.button);
}
if (options.style instanceof ol_style_Style) {
this._style = options.style;
} else {
this._style = new ol_style_Style({
text: new ol_style_Text(),
stroke: new ol_style_Stroke({
width: 1.5,
color: '#369'
})
});
}
if (!this._style.getText()) this._style.setText(new ol_style_Text());
var div_inner = document.createElement("div");
div_inner.classList.add("ol-inner");
element.appendChild(div_inner);
var div = document.createElement("div");
div.style.position = "relative";
div_inner.appendChild(div);
var ratio = this.ratio = 2;
this.canvas_ = document.createElement('canvas');
this.canvas_.width = (options.width || 300)*ratio;
this.canvas_.height = (options.height || 150)*ratio;
var styles = {
"msTransform":"scale(0.5,0.5)", "msTransformOrigin":"0 0",
"webkitTransform":"scale(0.5,0.5)", "webkitTransformOrigin":"0 0",
"mozTransform":"scale(0.5,0.5)", "mozTransformOrigin":"0 0",
"transform":"scale(0.5,0.5)", "transformOrigin":"0 0"
};
Object.keys(styles).forEach(function(style) {
if (style in self.canvas_.style) {
self.canvas_.style[style] = styles[style];
}
});
var div_to_canvas = document.createElement("div");
div.appendChild(div_to_canvas);
div_to_canvas.style.width = this.canvas_.width/ratio + "px";
div_to_canvas.style.height = this.canvas_.height/ratio + "px";
div_to_canvas.appendChild(this.canvas_);
div_to_canvas.addEventListener("click", function(e){ self.onMove(e); });
div_to_canvas.addEventListener("mousemove", function(e){ self.onMove(e); });
ol_control_Control.call(this, {
element: element,
target: options.target
});
// Offset in px
this.margin_ = { top:10*ratio, left:40*ratio, bottom:30*ratio, right:10*ratio };
if (!this.info.ytitle) this.margin_.left -= 20*ratio;
if (!this.info.xtitle) this.margin_.bottom -= 20*ratio;
// Cursor
this.bar_ = document.createElement("div");
this.bar_.classList.add("ol-profilbar");
this.bar_.style.top = (this.margin_.top/ratio)+"px";
this.bar_.style.height = (this.canvas_.height-this.margin_.top-this.margin_.bottom)/ratio+"px";
div.appendChild(this.bar_);
this.cursor_ = document.createElement("div");
this.cursor_.classList.add("ol-profilcursor");
div.appendChild(this.cursor_);
this.popup_ = document.createElement("div");
this.popup_.classList.add("ol-profilpopup");
this.cursor_.appendChild(this.popup_);
// Track information
var t = document.createElement("table");
t.cellPadding = '0';
t.cellSpacing = '0';
t.style.clientWidth = this.canvas_.width/ratio + "px";
div.appendChild(t);
var firstTr = document.createElement("tr");
firstTr.classList.add("track-info");
t.appendChild(firstTr);
var div_zmin = document.createElement("td");
div_zmin.innerHTML = (this.info.zmin||"Zmin")+': <span class="zmin">';
firstTr.appendChild(div_zmin);
var div_zmax = document.createElement("td");
div_zmax.innerHTML = (this.info.zmax||"Zmax")+': <span class="zmax">';
firstTr.appendChild(div_zmax);
var div_distance = document.createElement("td");
div_distance.innerHTML = (this.info.distance||"Distance")+': <span class="dist">';
firstTr.appendChild(div_distance);
var div_time = document.createElement("td");
div_time.innerHTML = (this.info.time||"Time")+': <span class="time">';
firstTr.appendChild(div_time);
var secondTr = document.createElement("tr");
secondTr.classList.add("point-info")
t.appendChild(secondTr);
var div_altitude = document.createElement("td");
div_altitude.innerHTML = (this.info.altitude||"Altitude")+': <span class="z">';
secondTr.appendChild(div_altitude);
var div_distance2 = document.createElement("td");
div_distance2.innerHTML = (this.info.distance||"Distance")+': <span class="dist">';
secondTr.appendChild(div_distance2);
var div_time2 = document.createElement("td");
div_time2.innerHTML = (this.info.time||"Time")+': <span class="time">';
secondTr.appendChild(div_time2);
// Array of data
this.tab_ = [];
// Show feature
if (options.feature) {
this.setGeometry (options.feature);
}
};
ol_ext_inherits(ol_control_Profil, ol_control_Control);
/** Custom infos list
* @api stable
*/
ol_control_Profil.prototype.info = {
"zmin": "Zmin",
"zmax": "Zmax",
"ytitle": "Altitude (m)",
"xtitle": "Distance (km)",
"time": "Time",
"altitude": "Altitude",
"distance": "Distance",
"altitudeUnits": "m",
"distanceUnitsM": "m",
"distanceUnitsKM": "km",
};
/** Show popup info
* @param {string} info to display as a popup
* @api stable
*/
ol_control_Profil.prototype.popup = function(info) {
this.popup_.innerHTML = info;
};
/** Show point on profil
* @param {*} p
* @param {number} dx
* @private
*/
ol_control_Profil.prototype._drawAt = function(p, dx) {
if (p) {
this.cursor_.style.left = dx+"px";
this.cursor_.style.top = (this.canvas_.height-this.margin_.bottom+p[1]*this.scale_[1]+this.dy_)/this.ratio+"px";
this.cursor_.style.display = "block";
this.bar_.parentElement.classList.add("over");
this.bar_.style.left = dx+"px";
this.bar_.style.display = "block";
this.element.querySelector(".point-info .z").textContent = p[1]+this.info.altitudeUnits;
this.element.querySelector(".point-info .dist").textContent = (p[0]/1000).toFixed(1)+this.info.distanceUnitsKM;
this.element.querySelector(".point-info .time").textContent = p[2];
if (dx>this.canvas_.width/this.ratio/2) this.popup_.classList.add('ol-left');
else this.popup_.classList.remove('ol-left');
} else {
this.cursor_.style.display = "none";
this.bar_.style.display = 'none';
this.cursor_.style.display = 'none';
this.bar_.parentElement.classList.remove("over");
}
};
/** Show point at coordinate or a distance on the profil
* @param { ol.coordinates||number } where a coordiniate or a distance from begining, if none it will hide the point
* @return { ol.coordinates } current point
*/
ol_control_Profil.prototype.showAt = function(where) {
var i, p, p0, d0 = Infinity;
if (typeof(where) === 'undefined') {
if (this.bar_.parentElement.classList.contains("over")) {
// Remove it
this._drawAt();
}
} else if (where.length) {
// Look for closest the point
for (i=1; p=this.tab_[i]; i++) {
var d = ol_coordinate_dist2d(p[3], where);
if (d<d0) {
p0 = p;
d0 = d;
}
}
} else {
for (i=0; p=this.tab_[i]; i++) {
p0 = p;
if (p[0] > where) {
break;
}
}
}
if (p0) {
var dx = (p0[0] * this.scale_[0] + this.margin_.left) / this.ratio;
this._drawAt(p0, dx);
return p0[3];
}
return null;
};
/** Get the point at a given time on the profil
* @param { number } time time at which to show the point
* @return { ol.coordinates } current point
*/
ol_control_Profil.prototype.pointAtTime = function(time) {
var i, p;
// Look for closest the point
for (i=1; p=this.tab_[i]; i++) {
var t = p[3][3];
if (t >= time) {
// Previous one ?
var pt = this.tab_[i-1][3];
if ((pt[3]+t)/2 < time) return pt;
else return p;
}
}
return this.tab_[this.tab_.length-1][3];
};
/** Mouse move over canvas
*/
ol_control_Profil.prototype.onMove = function(e) {
if (!this.tab_.length) return;
var box_canvas = this.canvas_.getBoundingClientRect();
var pos = {
top: box_canvas.top + window.pageYOffset - document.documentElement.clientTop,
left: box_canvas.left + window.pageXOffset - document.documentElement.clientLeft
};
var dx = e.pageX -pos.left;
var dy = e.pageY -pos.top;
var ratio = this.ratio;
if (dx>this.margin_.left/ratio && dx<(this.canvas_.width-this.margin_.right)/ratio
&& dy>this.margin_.top/ratio && dy<(this.canvas_.height-this.margin_.bottom)/ratio) {
var d = (dx*ratio-this.margin_.left)/this.scale_[0];
var p0 = this.tab_[0];
for (var i=1, p; p=this.tab_[i]; i++) {
if (p[0]>=d) {
if (d < (p[0]+p0[0])/2) p = p0;
break;
}
}
this._drawAt(p, dx);
this.dispatchEvent({ type:'over', click:e.type=="click", coord: p[3], time: p[2], distance: p[0] });
} else {
if (this.bar_.parentElement.classList.contains("over")) {
this._drawAt();
this.dispatchEvent({ type:'out' });
}
}
};
/** Show panel
* @api stable
*/
ol_control_Profil.prototype.show = function() {
this.element.classList.remove("ol-collapsed");
this.dispatchEvent({ type:'show', show: true });
};
/** Hide panel
* @api stable
*/
ol_control_Profil.prototype.hide = function() {
this.element.classList.add("ol-collapsed");
this.dispatchEvent({ type:'show', show: false });
};
/** Toggle panel
* @api stable
*/
ol_control_Profil.prototype.toggle = function() {
this.element.classList.toggle("ol-collapsed");
var b = this.element.classList.contains("ol-collapsed");
this.dispatchEvent({ type:'show', show: !b });
}
/** Is panel visible
*/
ol_control_Profil.prototype.isShown = function() {
return (!this.element.classList.contains("ol-collapsed"));
}
/**
* Set the geometry to draw the profil.
* @param {ol.Feature|ol.geom.Geometry} f the feature.
* @param {Object=} options
* @param {ol.ProjectionLike} options.projection feature projection, default projection of the map
* @param {string} options.zunit 'm' or 'km', default m
* @param {string} options.unit 'm' or 'km', default km
* @param {Number|undefined} options.zmin default 0
* @param {Number|undefined} options.zmax default max Z of the feature
* @param {Number|undefined} options.graduation z graduation default 100
* @param {integer|undefined} options.amplitude amplitude of the altitude, default zmax-zmin
* @api stable
*/
ol_control_Profil.prototype.setGeometry = function(g, options) {
if (!options) options = {};
if (g instanceof ol_Feature) g = g.getGeometry();
var canvas = this.canvas_;
var ctx = canvas.getContext('2d');
var w = canvas.width;
var h = canvas.height;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0,0, w, h);
// No Z
if (!/Z/.test(g.getLayout())) return;
// No time
if(/M/.test(g.getLayout())) this.element.querySelector(".time").parentElement.style.display = 'block';
else this.element.querySelector(".time").parentElement.style.display = 'none';
// Coords
var c = g.getCoordinates();
switch (g.getType()) {
case "LineString": break;
case "MultiLineString": c = c[0]; break;
default: return;
}
// Distance beetween 2 coords
var proj = options.projection || this.getMap().getView().getProjection();
function dist2d(p1,p2) {
return ol_sphere_getDistance(
ol_proj_transform(p1, proj, 'EPSG:4326'),
ol_proj_transform(p2, proj, 'EPSG:4326')
);
}
function getTime(t0, t1) {
if (!t0 || !t1) return "-"
var dt = (t1-t0) / 60; // mn
var ti = Math.trunc(dt/60);
var mn = Math.trunc(dt-ti*60);
return ti+"h"+(mn<10?"0":"")+mn+"mn";
}
// Margin
ctx.setTransform(1, 0, 0, 1, this.margin_.left, h-this.margin_.bottom);
var ratio = this.ratio;
w -= this.margin_.right + this.margin_.left;
h -= this.margin_.top + this.margin_.bottom;
// Draw axes
ctx.strokeStyle = this._style.getText().getFill().getColor() || '#000';
ctx.lineWidth = 0.5*ratio;
ctx.beginPath();
ctx.moveTo(0,0); ctx.lineTo(0,-h);
ctx.moveTo(0,0); ctx.lineTo(w, 0);
ctx.stroke();
// Calculate [distance, altitude, time, point] for each points
var zmin=Infinity, zmax=-Infinity;
var i, p, d, z, ti, t = this.tab_ = [];
for (i=0, p; p=c[i]; i++) {
z = p[2];
if (z<zmin) zmin=z;
if (z>zmax) zmax=z;
if (i==0) d = 0;
else d += dist2d(c[i-1], p);
ti = getTime(c[0][3],p[3]);
t.push ([d, z, ti, p]);
}
// Info
this.element.querySelector(".track-info .zmin").textContent = zmin.toFixed(2)+this.info.altitudeUnits;
this.element.querySelector(".track-info .zmax").textContent = zmax.toFixed(2)+this.info.altitudeUnits;
if (d>1000) {
this.element.querySelector(".track-info .dist").textContent = (d/1000).toFixed(1)+this.info.distanceUnitsKM;
} else {
this.element.querySelector(".track-info .dist").textContent= (d).toFixed(1)+this.info.distanceUnitsM;
}
this.element.querySelector(".track-info .time").textContent = ti;
// Set graduation
var grad = options.graduation || 100;
while (true) {
zmax = Math.ceil(zmax/grad)*grad;
zmin = Math.floor(zmin/grad)*grad;
var nbgrad = (zmax-zmin)/grad;
if (h/nbgrad < 15*ratio) {
grad *= 2;
}
else break;
}
// Set amplitude
if (typeof(options.zmin)=='number' && zmin > options.zmin) zmin = options.zmin;
if (typeof(options.zmax)=='number' && zmax < options.zmax) zmax = options.zmax;
var amplitude = options.amplitude;
if (amplitude) {
zmax = Math.max (zmin + amplitude, zmax);
}
// Scales lines
var scx = w/d;
var scy = -h/(zmax-zmin);
var dy = this.dy_ = -zmin*scy;
this.scale_ = [scx,scy];
// Draw Path
ctx.beginPath();
for (i=0; p=t[i]; i++) {
if (i==0) ctx.moveTo(p[0]*scx,p[1]*scy+dy);
else ctx.lineTo(p[0]*scx,p[1]*scy+dy);
}
if (this._style.getStroke()) {
ctx.strokeStyle = this._style.getStroke().getColor() || '#000';
ctx.lineWidth = this._style.getStroke().getWidth() * ratio;
ctx.setLineDash([]);
ctx.stroke();
}
// Fill path
if (this._style.getFill()) {
ctx.fillStyle = this._style.getFill().getColor() || '#000';
ctx.Style = this._style.getFill().getColor() || '#000';
ctx.lineTo(t[t.length-1][0]*scx, 0);
ctx.lineTo(t[0][0]*scx, 0);
ctx.fill();
}
// Draw
ctx.font = (10*ratio)+'px arial';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
ctx.fillStyle = this._style.getText().getFill().getColor() || '#000';
// Scale Z
ctx.beginPath();
for (i=zmin; i<=zmax; i+=grad) {
if (options.zunit!="km") ctx.fillText(i, -4*ratio, i*scy+dy);
else ctx.fillText((i/1000).toFixed(1), -4*ratio, i*scy+dy);
ctx.moveTo (-2*ratio, i*scy+dy);
if (i!=0) ctx.lineTo (d*scx, i*scy+dy);
else ctx.lineTo (0, i*scy+dy);
}
// Scale X
ctx.textAlign = "center";
ctx.textBaseline = "top";
ctx.setLineDash([ratio,3*ratio]);
var unit = options.unit ||"km";
var step;
if (d>1000) {
step = Math.round(d/1000)*100;
if (step > 1000) step = Math.ceil(step/1000)*1000;
} else {
unit = "m";
if (d>100) step = Math.round(d/100)*10;
else if (d>10) step = Math.round(d/10);
else if (d>1) step = Math.round(d)/10;
else step = d;
}
for (i=0; i<=d; i+=step) {
var txt = (unit=="m") ? i : (i/1000);
//if (i+step>d) txt += " "+ (options.zunits || "km");
ctx.fillText(Math.round(txt*10)/10, i*scx, 4*ratio);
ctx.moveTo (i*scx, 2*ratio); ctx.lineTo (i*scx, 0);
}
ctx.font = (12*ratio)+"px arial";
ctx.fillText(this.info.xtitle.replace("(km)","("+unit+")"), w/2, 18*ratio);
ctx.save();
ctx.rotate(-Math.PI/2);
ctx.fillText(this.info.ytitle, h/2, -this.margin_.left);
ctx.restore();
ctx.stroke();
};
/** Get profil image
* @param {string|undefined} type image format or 'canvas' to get the canvas image, default image/png.
* @param {Number|undefined} encoderOptions between 0 and 1 indicating image quality image/jpeg or image/webp, default 0.92.
* @return {string} requested data uri
* @api stable
*/
ol_control_Profil.prototype.getImage = function(type, encoderOptions) {
if (type==="canvas") return this.canvas_;
return this.canvas_.toDataURL(type, encoderOptions);
};
export default ol_control_Profil