leaflet.eleation.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. L.Control.Elevation = L.Control.extend({
  2. options: {
  3. position: "bottomcenter",
  4. theme: "lime-theme",
  5. width: 600,
  6. height: 175,
  7. margins: {
  8. top: 10,
  9. right: 20,
  10. bottom: 30,
  11. left: 60
  12. },
  13. useHeightIndicator: true,
  14. interpolation: "linear",
  15. hoverNumber: {
  16. decimalsX: 3,
  17. decimalsY: 0,
  18. formatter: undefined
  19. },
  20. xTicks: undefined,
  21. yTicks: undefined,
  22. collapsed: false,
  23. yAxisMin: undefined,
  24. yAxisMax: undefined,
  25. forceAxisBounds: false
  26. },
  27. onRemove: function(map) {
  28. this._container = null;
  29. },
  30. onAdd: function(map) {
  31. this._map = map;
  32. if(ONEMAP.M.pcLayout.status.showSideBar){
  33. var curWidth = $(window).width()-410;
  34. }else{
  35. var curWidth = $(window).width();
  36. }
  37. var opts = this.options;
  38. var margin = opts.margins;
  39. opts.width = curWidth;
  40. opts.xTicks = opts.xTicks || Math.round(this._width() / 75);
  41. opts.yTicks = opts.yTicks || Math.round(this._height() / 30);
  42. opts.hoverNumber.formatter = opts.hoverNumber.formatter || this._formatter;
  43. //append theme name on body
  44. d3.select("body").classed(opts.theme, true);
  45. var x = this._x = d3.scale.linear()
  46. .range([0, this._width()]);
  47. var y = this._y = d3.scale.linear()
  48. .range([this._height(), 0]);
  49. var area = this._area = d3.svg.area()
  50. .interpolate(opts.interpolation)
  51. .x(function(d) {
  52. return x(d.dist);
  53. })
  54. .y0(this._height())
  55. .y1(function(d) {
  56. return y(d.altitude);
  57. });
  58. var container = this._container = L.DomUtil.create("div", "elevation leaflet-control-elevation");
  59. this._initToggle();
  60. var cont = d3.select(container);
  61. cont.attr("width", opts.width);
  62. var svg = cont.append("svg");
  63. svg.attr("width", opts.width)
  64. .attr("class", "background")
  65. .attr("height", opts.height)
  66. .append("g")
  67. .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  68. var line = d3.svg.line();
  69. line = line
  70. .x(function(d) {
  71. return d3.mouse(svg.select("g"))[0];
  72. })
  73. .y(function(d) {
  74. return this._height();
  75. });
  76. var g = d3.select(this._container).select("svg").select("g");
  77. this._areapath = g.append("path")
  78. .attr("class", "area");
  79. var background = this._background = g.append("rect")
  80. .attr("width", this._width())
  81. .attr("height", this._height())
  82. .style("fill", "none")
  83. .style("fillOpacity", "0")
  84. .style("stroke", "none")
  85. .style("pointer-events", "all");
  86. //if (L.Browser.touch) {
  87. // background.on("touchmove.drag", this._dragHandler.bind(this)).
  88. // on("touchstart.drag", this._dragStartHandler.bind(this)).
  89. // on("touchstart.focus", this._mousemoveHandler.bind(this));
  90. // L.DomEvent.on(this._container, 'touchend', this._dragEndHandler, this);
  91. //} else {
  92. background.on("mousemove.focus", this._mousemoveHandler.bind(this)).
  93. on("mouseout.focus", this._mouseoutHandler.bind(this)).
  94. on("mousedown.drag", this._dragStartHandler.bind(this)).
  95. on("mousemove.drag", this._dragHandler.bind(this));
  96. L.DomEvent.on(this._container, 'mouseup', this._dragEndHandler, this);
  97. //}
  98. this._xaxisgraphicnode = g.append("g");
  99. this._yaxisgraphicnode = g.append("g");
  100. this._appendXaxis(this._xaxisgraphicnode);
  101. this._appendYaxis(this._yaxisgraphicnode);
  102. var focusG = this._focusG = g.append("g");
  103. this._mousefocus = focusG.append('svg:line')
  104. .attr('class', 'mouse-focus-line')
  105. .attr('x2', '0')
  106. .attr('y2', '0')
  107. .attr('x1', '0')
  108. .attr('y1', '0');
  109. this._focuslabelX = focusG.append("svg:text")
  110. .style("pointer-events", "none")
  111. .attr("class", "mouse-focus-label-x");
  112. this._focuslabelY = focusG.append("svg:text")
  113. .style("pointer-events", "none")
  114. .attr("class", "mouse-focus-label-y");
  115. if (this._data) {
  116. this._applyData();
  117. }
  118. return container;
  119. },
  120. _dragHandler: function() {
  121. //we don´t want map events to occur here
  122. d3.event.preventDefault();
  123. d3.event.stopPropagation();
  124. this._gotDragged = true;
  125. this._drawDragRectangle();
  126. },
  127. /*
  128. * Draws the currently dragged rectabgle over the chart.
  129. */
  130. _drawDragRectangle: function() {
  131. if (!this._dragStartCoords) {
  132. return;
  133. }
  134. var dragEndCoords = this._dragCurrentCoords = d3.mouse(this._background.node());
  135. var x1 = Math.min(this._dragStartCoords[0], dragEndCoords[0]),
  136. x2 = Math.max(this._dragStartCoords[0], dragEndCoords[0]);
  137. if (!this._dragRectangle && !this._dragRectangleG) {
  138. var g = d3.select(this._container).select("svg").select("g");
  139. this._dragRectangleG = g.append("g");
  140. this._dragRectangle = this._dragRectangleG.append("rect")
  141. .attr("width", x2 - x1)
  142. .attr("height", this._height())
  143. .attr("x", x1)
  144. .attr('class', 'mouse-drag')
  145. .style("pointer-events", "none")
  146. .style("opacity", "0.4");
  147. } else {
  148. this._dragRectangle.attr("width", x2 - x1)
  149. .attr("x", x1);
  150. }
  151. },
  152. /*
  153. * Removes the drag rectangle and zoms back to the total extent of the data.
  154. */
  155. _resetDrag: function() {
  156. if (this._dragRectangleG) {
  157. this._dragRectangleG.remove();
  158. this._dragRectangleG = null;
  159. this._dragRectangle = null;
  160. this._hidePositionMarker();
  161. this._map.fitBounds(this._fullExtent);
  162. }
  163. },
  164. /*
  165. * Handles end of dragg operations. Zooms the map to the selected items extent.
  166. */
  167. _dragEndHandler: function() {
  168. if (!this._dragStartCoords || !this._gotDragged) {
  169. this._dragStartCoords = null;
  170. this._gotDragged = false;
  171. this._resetDrag();
  172. return;
  173. }
  174. this._hidePositionMarker();
  175. var item1 = this._findItemForX(this._dragStartCoords[0]),
  176. item2 = this._findItemForX(this._dragCurrentCoords[0]);
  177. this._fitSection(item1, item2);
  178. this._dragStartCoords = null;
  179. this._gotDragged = false;
  180. },
  181. _dragStartHandler: function() {
  182. d3.event.preventDefault();
  183. d3.event.stopPropagation();
  184. this._gotDragged = false;
  185. this._dragStartCoords = d3.mouse(this._background.node());
  186. },
  187. /*
  188. * Finds a data entry for a given x-coordinate of the diagram
  189. */
  190. _findItemForX: function(x) {
  191. var bisect = d3.bisector(function(d) {
  192. return d.dist;
  193. }).left;
  194. var xinvert = this._x.invert(x);
  195. return bisect(this._data, xinvert);
  196. },
  197. /** Make the map fit the route section between given indexes. */
  198. _fitSection: function(index1, index2) {
  199. var start = Math.min(index1, index2),
  200. end = Math.max(index1, index2);
  201. var ext = this._calculateFullExtent(this._data.slice(start, end));
  202. this._map.fitBounds(ext);
  203. },
  204. _initToggle: function() {
  205. /* inspired by L.Control.Layers */
  206. var container = this._container;
  207. //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released
  208. container.setAttribute('aria-haspopup', true);
  209. if (!L.Browser.touch) {
  210. L.DomEvent
  211. .disableClickPropagation(container);
  212. //.disableScrollPropagation(container);
  213. } else {
  214. L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
  215. }
  216. if (this.options.collapsed) {
  217. this._collapse();
  218. if (!L.Browser.android) {
  219. L.DomEvent
  220. .on(container, 'mouseover', this._expand, this)
  221. .on(container, 'mouseout', this._collapse, this);
  222. }
  223. var link = this._button = L.DomUtil.create('a', 'elevation-toggle', container);
  224. link.href = '#';
  225. link.title = 'Elevation';
  226. if (L.Browser.touch) {
  227. L.DomEvent
  228. .on(link, 'click', L.DomEvent.stop)
  229. .on(link, 'click', this._expand, this);
  230. } else {
  231. L.DomEvent.on(link, 'focus', this._expand, this);
  232. }
  233. this._map.on('click', this._collapse, this);
  234. // TODO keyboard accessibility
  235. }
  236. },
  237. _expand: function() {
  238. this._container.className = this._container.className.replace(' elevation-collapsed', '');
  239. },
  240. _collapse: function() {
  241. L.DomUtil.addClass(this._container, 'elevation-collapsed');
  242. },
  243. _width: function() {
  244. var opts = this.options;
  245. return opts.width - opts.margins.left - opts.margins.right;
  246. },
  247. _height: function() {
  248. var opts = this.options;
  249. return opts.height - opts.margins.top - opts.margins.bottom;
  250. },
  251. /*
  252. * Fromatting funciton using the given decimals and seperator
  253. */
  254. _formatter: function(num, dec, sep) {
  255. var res;
  256. if (dec === 0) {
  257. res = Math.round(num) + "";
  258. } else {
  259. res = L.Util.formatNum(num, dec) + "";
  260. }
  261. var numbers = res.split(".");
  262. if (numbers[1]) {
  263. var d = dec - numbers[1].length;
  264. for (; d > 0; d--) {
  265. numbers[1] += "0";
  266. }
  267. res = numbers.join(sep || ".");
  268. }
  269. return res;
  270. },
  271. _appendYaxis: function(y) {
  272. y.attr("class", "y axis")
  273. .call(d3.svg.axis()
  274. .scale(this._y)
  275. .ticks(this.options.yTicks)
  276. .orient("left"))
  277. .append("text")
  278. .attr("x", -45)
  279. .attr("y", 3)
  280. .style("text-anchor", "end")
  281. .text("米");
  282. },
  283. _appendXaxis: function(x) {
  284. x.attr("class", "x axis")
  285. .attr("transform", "translate(0," + this._height() + ")")
  286. .call(d3.svg.axis()
  287. .scale(this._x)
  288. .ticks(this.options.xTicks)
  289. .orient("bottom"))
  290. .append("text")
  291. .attr("x", this._width() + 20)
  292. .attr("y", 15)
  293. .style("text-anchor", "end")
  294. .text("公里");
  295. },
  296. _updateAxis: function() {
  297. this._xaxisgraphicnode.selectAll("g").remove();
  298. this._xaxisgraphicnode.selectAll("path").remove();
  299. this._xaxisgraphicnode.selectAll("text").remove();
  300. this._yaxisgraphicnode.selectAll("g").remove();
  301. this._yaxisgraphicnode.selectAll("path").remove();
  302. this._yaxisgraphicnode.selectAll("text").remove();
  303. this._appendXaxis(this._xaxisgraphicnode);
  304. this._appendYaxis(this._yaxisgraphicnode);
  305. },
  306. _mouseoutHandler: function() {
  307. this._hidePositionMarker();
  308. },
  309. /*
  310. * Hides the position-/heigth indication marker drawn onto the map
  311. */
  312. _hidePositionMarker: function() {
  313. if (this._marker) {
  314. this._map.removeLayer(this._marker);
  315. this._marker = null;
  316. }
  317. if (this._mouseHeightFocus) {
  318. this._mouseHeightFocus.style("visibility", "hidden");
  319. this._mouseHeightFocusLabel.style("visibility", "hidden");
  320. }
  321. if (this._pointG) {
  322. this._pointG.style("visibility", "hidden");
  323. }
  324. this._focusG.style("visibility", "hidden");
  325. },
  326. /*
  327. * Handles the moueseover the chart and displays distance and altitude level
  328. */
  329. _mousemoveHandler: function(d, i, ctx) {
  330. if (!this._data || this._data.length === 0) {
  331. return;
  332. }
  333. var coords = d3.mouse(this._background.node());
  334. var opts = this.options;
  335. this._focusG.style("visibility", "visible");
  336. this._mousefocus.attr('x1', coords[0])
  337. .attr('y1', 0)
  338. .attr('x2', coords[0])
  339. .attr('y2', this._height())
  340. .classed('hidden', false);
  341. var bisect = d3.bisector(function(d) {
  342. return d.dist;
  343. }).left;
  344. var item = this._data[this._findItemForX(coords[0])],
  345. alt = item.altitude,
  346. dist = item.dist,
  347. ll = item.latlng,
  348. numY = opts.hoverNumber.formatter(alt, opts.hoverNumber.decimalsY),
  349. numX = opts.hoverNumber.formatter(dist, opts.hoverNumber.decimalsX);
  350. this._focuslabelX.attr("x", coords[0])
  351. .text(numY + " 米");
  352. this._focuslabelY.attr("y", this._height() - 5)
  353. .attr("x", coords[0])
  354. .text(numX + " 公里");
  355. var layerpoint = this._map.latLngToLayerPoint(ll);
  356. //if we use a height indicator we create one with SVG
  357. //otherwise we show a marker
  358. if (opts.useHeightIndicator) {
  359. if (!this._mouseHeightFocus) {
  360. var heightG = d3.select(".leaflet-overlay-pane svg")
  361. .append("g");
  362. this._mouseHeightFocus = heightG.append('svg:line')
  363. .attr('class', 'height-focus line')
  364. .attr('x2', '0')
  365. .attr('y2', '0')
  366. .attr('x1', '0')
  367. .attr('y1', '0');
  368. var pointG = this._pointG = heightG.append("g");
  369. pointG.append("svg:circle")
  370. .attr("r", 6)
  371. .attr("cx", 0)
  372. .attr("cy", 0)
  373. .attr("class", "height-focus circle-lower");
  374. this._mouseHeightFocusLabel = heightG.append("svg:text")
  375. .attr("class", "height-focus-label")
  376. .style("pointer-events", "none");
  377. }
  378. //var normalizedAlt = this._height() / this._maxElevation * alt;
  379. var normalizedAlt = 50;
  380. var normalizedY = layerpoint.y - normalizedAlt;
  381. //var normalizedY = layerpoint.y;
  382. this._mouseHeightFocus.attr("x1", layerpoint.x)
  383. .attr("x2", layerpoint.x)
  384. .attr("y1", layerpoint.y)
  385. .attr("y2", normalizedY)
  386. .style("visibility", "visible");
  387. this._pointG.attr("transform", "translate(" + layerpoint.x + "," + layerpoint.y + ")")
  388. .style("visibility", "visible");
  389. this._mouseHeightFocusLabel.attr("x", layerpoint.x)
  390. .attr("y", normalizedY)
  391. .text(numY + " 米")
  392. .style("visibility", "visible");
  393. } else {
  394. /*if (!this._marker) {
  395. this._marker = new L.Marker(ll).addTo(this._map);
  396. } else {
  397. this._marker.setLatLng(ll);
  398. }*/
  399. }
  400. if (!this._marker) {
  401. this._marker = new L.Marker(ll).addTo(this._map);
  402. } else {
  403. this._marker.setLatLng(ll);
  404. }
  405. },
  406. /*
  407. * Parsing of GeoJSON data lines and their elevation in z-coordinate
  408. */
  409. _addGeoJSONData: function(coords) {
  410. if (coords) {
  411. var data = this._data || [];
  412. var dist = this._dist || 0;
  413. var ele = this._maxElevation || 0;
  414. for (var i = 0; i < coords.length; i++) {
  415. var s = new L.LatLng(coords[i][1], coords[i][0]);
  416. var e = new L.LatLng(coords[i ? i - 1 : 0][1], coords[i ? i - 1 : 0][0]);
  417. var newdist = s.distanceTo(e);
  418. dist = dist + Math.round(newdist / 1000 * 100000) / 100000;
  419. ele = ele < coords[i][2] ? coords[i][2] : ele;
  420. data.push({
  421. dist: dist,
  422. altitude: coords[i][2],
  423. x: coords[i][0],
  424. y: coords[i][1],
  425. latlng: s
  426. });
  427. }
  428. this._dist = dist;
  429. this._data = data;
  430. this._maxElevation = ele;
  431. }
  432. },
  433. /*
  434. * Parsing function for GPX data as used by https://github.com/mpetazzoni/leaflet-gpx
  435. */
  436. _addGPXdata: function(coords) {
  437. if (coords) {
  438. var data = this._data || [];
  439. var dist = this._dist || 0;
  440. var ele = this._maxElevation || 0;
  441. for (var i = 0; i < coords.length; i++) {
  442. var s = coords[i];
  443. var e = coords[i ? i - 1 : 0];
  444. var newdist = s.distanceTo(e);
  445. dist = dist + Math.round(newdist / 1000 * 100000) / 100000;
  446. ele = ele < s.meta.ele ? s.meta.ele : ele;
  447. data.push({
  448. dist: dist,
  449. altitude: s.meta.ele,
  450. x: s.lng,
  451. y: s.lat,
  452. latlng: s
  453. });
  454. }
  455. this._dist = dist;
  456. this._data = data;
  457. this._maxElevation = ele;
  458. }
  459. },
  460. _addData: function(d) {
  461. var geom = d && d.geometry && d.geometry;
  462. var i;
  463. if (geom) {
  464. switch (geom.type) {
  465. case 'LineString':
  466. this._addGeoJSONData(geom.coordinates);
  467. break;
  468. case 'MultiLineString':
  469. for (i = 0; i < geom.coordinates.length; i++) {
  470. this._addGeoJSONData(geom.coordinates[i]);
  471. }
  472. break;
  473. default:
  474. throw new Error('Invalid GeoJSON object.');
  475. }
  476. }
  477. var feat = d && d.type === "FeatureCollection";
  478. if (feat) {
  479. for (i = 0; i < d.features.length; i++) {
  480. this._addData(d.features[i]);
  481. }
  482. }
  483. if (d && d._latlngs) {
  484. this._addGPXdata(d._latlngs);
  485. }
  486. },
  487. /*
  488. * Calculates the full extent of the data array
  489. */
  490. _calculateFullExtent: function(data) {
  491. if (!data || data.length < 1) {
  492. throw new Error("no data in parameters");
  493. }
  494. var ext = new L.latLngBounds(data[0].latlng, data[0].latlng);
  495. data.forEach(function(item) {
  496. ext.extend(item.latlng);
  497. });
  498. return ext;
  499. },
  500. /*
  501. * Add data to the diagram either from GPX or GeoJSON and
  502. * update the axis domain and data
  503. */
  504. addData: function(d) {
  505. this._addData(d);
  506. if (this._container) {
  507. this._applyData();
  508. }
  509. },
  510. _applyData: function() {
  511. var xdomain = d3.extent(this._data, function(d) {
  512. return d.dist;
  513. });
  514. var ydomain = d3.extent(this._data, function(d) {
  515. return d.altitude;
  516. });
  517. var opts = this.options;
  518. if (opts.yAxisMin !== undefined && (opts.yAxisMin < ydomain[0] || opts.forceAxisBounds)) {
  519. ydomain[0] = opts.yAxisMin;
  520. }
  521. if (opts.yAxisMax !== undefined && (opts.yAxisMax > ydomain[1] || opts.forceAxisBounds)) {
  522. ydomain[1] = opts.yAxisMax;
  523. }
  524. this._x.domain(xdomain);
  525. this._y.domain(ydomain);
  526. this._areapath.datum(this._data)
  527. .attr("d", this._area);
  528. this._updateAxis();
  529. this._fullExtent = this._calculateFullExtent(this._data);
  530. },
  531. /*
  532. * Reset data
  533. */
  534. _clearData: function() {
  535. this._data = null;
  536. this._dist = null;
  537. this._maxElevation = null;
  538. },
  539. /*
  540. * Reset data and display
  541. */
  542. clear: function() {
  543. this._clearData();
  544. if (!this._areapath) {
  545. return;
  546. }
  547. // workaround for 'Error: Problem parsing d=""' in Webkit when empty data
  548. // https://groups.google.com/d/msg/d3-js/7rFxpXKXFhI/HzIO_NPeDuMJ
  549. //this._areapath.datum(this._data).attr("d", this._area);
  550. this._areapath.attr("d", "M0 0");
  551. this._x.domain([0, 1]);
  552. this._y.domain([0, 1]);
  553. this._updateAxis();
  554. }
  555. });
  556. L.control.elevation = function(options) {
  557. return new L.Control.Elevation(options);
  558. };