mirror of
https://github.com/Viglino/ol-ext.git
synced 2025-12-08 19:26:29 +00:00
378 lines
11 KiB
JavaScript
378 lines
11 KiB
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).
|
|
*/
|
|
|
|
import ol from 'ol'
|
|
import ol_control_Control from 'ol/control/control'
|
|
import ol_Sphere from 'ol/sphere'
|
|
import ol_proj from 'ol/proj'
|
|
|
|
/**
|
|
* @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=} _ol_control_ opt_options.
|
|
*
|
|
*/
|
|
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 = $("<div>").addClass(options.className || "ol-profil");
|
|
}
|
|
else
|
|
{ element = $("<div>").addClass((options.className || 'ol-profil') +' ol-unselectable ol-control ol-collapsed');
|
|
this.button = $("<button>")
|
|
.attr('type','button')
|
|
.on("click touchstart", function(e)
|
|
{ self.toggle();
|
|
e.preventDefault();
|
|
})
|
|
.appendTo(element);
|
|
}
|
|
|
|
var div = $("<div>").addClass("ol-inner").appendTo(element);
|
|
div = $("<div>").css("position","relative").appendTo(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;
|
|
$(this.canvas_).css({
|
|
"transform":"scale(0.5,0.5)", "transform-origin":"0 0",
|
|
"-ms-transform":"scale(0.5,0.5)", "-ms-transform-origin":"0 0",
|
|
"-webkit-transform":"scale(0.5,0.5)", "-webkit-transform-origin":"0 0",
|
|
"transform":"scale(0.5,0.5)", "transform-origin":"0 0"
|
|
});
|
|
$("<div>").appendTo(div)
|
|
.width (this.canvas_.width/ratio)
|
|
.height (this.canvas_.height/ratio)
|
|
.append(this.canvas_)
|
|
.on("click mousemove", function(e){ self.onMove(e); });
|
|
|
|
ol_control_Control.call(this,
|
|
{ element: element.get(0),
|
|
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_ = $("<div>").addClass("ol-profilbar")
|
|
.css({top:(this.margin_.top/ratio)+"px", height:(this.canvas_.height-this.margin_.top-this.margin_.bottom)/ratio+"px" })
|
|
.appendTo(div);
|
|
this.cursor_ = $("<div>").addClass("ol-profilcursor")
|
|
.appendTo(div);
|
|
this.popup_ = $("<div>").addClass("ol-profilpopup")
|
|
.appendTo(this.cursor_);
|
|
|
|
// Track information
|
|
var t = $("<table cellpadding='0' cellspacing='0'>").appendTo(div).width(this.canvas_.width/ratio);
|
|
var tr = $("<tr>").addClass("track-info").appendTo(t);
|
|
$("<td>").html((this.info.zmin||"Zmin")+': <span class="zmin">').appendTo(tr);
|
|
$("<td>").html((this.info.zmax||"Zmax")+': <span class="zmax">').appendTo(tr);
|
|
$("<td>").html((this.info.distance||"Distance")+': <span class="dist">').appendTo(tr);
|
|
$("<td>").html((this.info.time||"Time")+': <span class="time">').appendTo(tr);
|
|
tr = $("<tr>").addClass("point-info").appendTo(t);
|
|
$("<td>").html((this.info.altitude||"Altitude")+': <span class="z">').appendTo(tr);
|
|
$("<td>").html((this.info.distance||"Distance")+': <span class="dist">').appendTo(tr);
|
|
$("<td>").html((this.info.time||"Time")+': <span class="time">').appendTo(tr);
|
|
|
|
// Array of data
|
|
this.tab_ = [];
|
|
|
|
// Show feature
|
|
if (options.feature)
|
|
{ this.setGeometry (options.feature);
|
|
}
|
|
};
|
|
ol.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"
|
|
};
|
|
|
|
/** Show popup info
|
|
* @param {string} info to display as a popup
|
|
* @api stable
|
|
*/
|
|
ol_control_Profil.prototype.popup = function(info)
|
|
{ this.popup_.html(info);
|
|
}
|
|
|
|
/** Mouse move over canvas
|
|
*/
|
|
ol_control_Profil.prototype.onMove = function(e)
|
|
{ if (!this.tab_.length) return;
|
|
var pos = $(this.canvas_).offset();
|
|
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)
|
|
{ this.bar_.css("left", dx+"px").show();
|
|
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;
|
|
}
|
|
}
|
|
if (p) this.cursor_.css({
|
|
left:dx+"px",
|
|
top:(this.canvas_.height-this.margin_.bottom+p[1]*this.scale_[1]+this.dy_)/ratio+"px"
|
|
}).show();
|
|
else this.cursor_.hide();
|
|
this.bar_.parent().addClass("over");
|
|
$(".point-info .z", this.element).text(p[1]+"m");
|
|
$(".point-info .dist", this.element).text((p[0]/1000).toFixed(1)+"km");
|
|
$(".point-info .time", this.element).text(p[2]);
|
|
if (dx>this.canvas_.width/ratio/2) this.popup_.addClass('ol-left');
|
|
else this.popup_.removeClass('ol-left');
|
|
this.dispatchEvent({ type:'over', click:e.type=="click", coord: p[3], time: p[2], distance: p[0] });
|
|
}
|
|
else
|
|
{ if (this.bar_.parent().hasClass("over"))
|
|
{ this.bar_.hide();
|
|
this.cursor_.hide();
|
|
this.bar_.parent().removeClass("over");
|
|
this.dispatchEvent({ type:'out' });
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Show panel
|
|
* @api stable
|
|
*/
|
|
ol_control_Profil.prototype.show = function()
|
|
{ $(this.element).removeClass("ol-collapsed");
|
|
this.dispatchEvent({ type:'show', show: true });
|
|
}
|
|
/** Hide panel
|
|
* @api stable
|
|
*/
|
|
ol_control_Profil.prototype.hide = function()
|
|
{ $(this.element).addClass("ol-collapsed");
|
|
this.dispatchEvent({ type:'show', show: false });
|
|
}
|
|
/** Toggle panel
|
|
* @api stable
|
|
*/
|
|
ol_control_Profil.prototype.toggle = function()
|
|
{ var b = $(this.element).toggleClass("ol-collapsed").hasClass("ol-collapsed");
|
|
this.dispatchEvent({ type:'show', show: !b });
|
|
}
|
|
/** Is panel visible
|
|
*/
|
|
ol_control_Profil.prototype.isShown = function()
|
|
{ return (!$(this.element).hasClass("ol-collapsed"));
|
|
}
|
|
|
|
/**
|
|
* Set the geometry to draw the profil.
|
|
* @param {ol.Feature|ol.geom} f the feature.
|
|
* @param {Object=} options
|
|
* - projection {ol.ProjectionLike} feature projection, default projection of the map
|
|
* - zunit {m|km} default m
|
|
* - unit {m|km} default km
|
|
* - zmin {Number|undefined} default 0
|
|
* - zmax {Number|undefined} default max Z of the feature
|
|
* - graduation {Number|undefined} z graduation default 100
|
|
* - amplitude {integer|undefined} 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())) $(".time", this.element).parent().show();
|
|
else $(".time", this.element).parent().hide();
|
|
|
|
// Coords
|
|
var c = g.getCoordinates();
|
|
switch (g.getType())
|
|
{ case "LineString": break;
|
|
case "MultiLineString": c = c[0]; break;
|
|
default: return;
|
|
}
|
|
|
|
// Distance beetween 2 coords
|
|
var wgs84Sphere = new ol_Sphere(6378137);
|
|
var proj = options.projection || this.getMap().getView().getProjection();
|
|
function dist2d(p1,p2)
|
|
{ return wgs84Sphere.haversineDistance(
|
|
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 = "#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();
|
|
|
|
//
|
|
var zmin=Infinity, zmax=-Infinity;
|
|
var d, z, ti, t = this.tab_ = [];
|
|
for (var 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
|
|
$(".track-info .zmin", this.element).text(zmin.toFixed(2)+"m");
|
|
$(".track-info .zmax", this.element).text(zmax.toFixed(2)+"m");
|
|
if (d>1000)
|
|
{ $(".track-info .dist", this.element).text((d/1000).toFixed(1)+"km");
|
|
}
|
|
else
|
|
{ $(".track-info .dist", this.element).text((d).toFixed(1)+"m");
|
|
}
|
|
$(".track-info .time", this.element).text(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
|
|
ctx.font = (10*ratio)+"px arial";
|
|
ctx.textAlign = "right";
|
|
ctx.textBaseline = "middle";
|
|
ctx.fillStyle="#000";
|
|
// Scale Z
|
|
ctx.beginPath();
|
|
for (var 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 (var 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();
|
|
|
|
//
|
|
ctx.strokeStyle = "#369";
|
|
ctx.lineWidth = 1;
|
|
ctx.setLineDash([]);
|
|
ctx.beginPath();
|
|
for (var i=0, p; 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);
|
|
}
|
|
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
|