现在的商场管理者在管理商场的同时面临着一些无法避免的问题比如:人员监管不到位、效率低下、商场同质化严重,人流量少等。发现了这些问题作为开发人员的我们怎能视而不见,我们的责任就是发现问题解决问题,提供更好更智能的服务。因此就此问题我们想出了相应的解决办法,使用JS+Three.js+Echart开发了一个功能界面,为商场管理者提供更加高效的管理方法。

  通过商场管理系统的相应界面,商场管理者可实时获取商场的人流数据、人流密度的热力分布、可实时查看商场各处的视频监控信息、安保人员的实时位置信息及运动轨迹。针对突发状况可以即时调度、快速处理。还可以依据大数据分析周边业态情况,为制定运营策略提供数据支持等。

  就以上的市场实际情况需求,开始了我的功能开发之旅。

  我使用ESMap的地图编辑器编辑好商场地图后,开始布局规划解决问题

开发流程如下:

  首先,实现一个商场客流量信息的饼状统计表,还有各个时间点的流量趋势和人群密度的线性图表。再实现一个控制面板,可以通过控制面板根据地图的热力图查看商场各个位置客流量以及各个位置的实时视频等,情况一目了然;最后做一个可以搜索店铺客流量及营业额情况的搜索框。

1.方便开发,先使用模拟数据创建图表,投入使用后自行接入后台数据即可。

(1)使用Echart创建统计客流量的饼状图:

  1. function circleSet() {
  2. myChart1 = echarts.init(document.getElementById('ec1'));
  3. myChart2 = echarts.init(document.getElementById('ec2'));
  4. var color= ['#b679fe', '#6271fd','#94d96c', '#0fbdd9','#f0f0f0'];
  5. var dataStyle = {
  6. normal: {
  7. label: {
  8. show: false
  9. },
  10. labelLine: {
  11. show: false
  12. },
  13. shadowBlur: 40,
  14. borderWidth: 10,
  15. shadowColor: 'rgba(0, 0, 0, 0)' //边框阴影
  16. }
  17. };
  18. //第一个饼状图
  19. var optionCircleA = {
  20. backgroundColor: '#fff',
  21. title: {
  22. text: '52452',
  23. x: 'center',
  24. y: 'center',
  25. textStyle: {
  26. fontWeight: 'normal',
  27. fontSize: 14,
  28. color: "#b679fe",
  29. }
  30. },
  31. series: [{
  32. name: 'Line 1',
  33. type: 'pie',
  34. clockWise: false,
  35. radius: [37, 45],
  36. center:['50%','50%'],
  37. itemStyle: dataStyle,
  38. hoverAnimation: false,
  39. startAngle: 90,
  40. label:{
  41. borderRadius:'10',
  42. },
  43. data: [{
  44. value: 54.6,
  45. name: '外',
  46. itemStyle: {
  47. normal: {
  48. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
  49. offset: 0,
  50. color:color[0]
  51. }, {
  52. offset: 1,
  53. color: color[1]
  54. }])
  55. }
  56. }
  57. },
  58. {
  59. value: 0,
  60. name: '',
  61. tooltip: {
  62. show: false
  63. },
  64. },
  65. ]
  66. },
  67. {
  68. name: 'Line 2',
  69. type: 'pie',
  70. clockWise: false,
  71. radius: [30, 32],
  72. center:['50%','50%'],
  73. itemStyle: dataStyle,
  74. hoverAnimation: false,
  75. startAngle: 90,
  76. data: [{
  77. value: 56.7,
  78. name: '内',
  79. itemStyle: {
  80. normal: {
  81. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
  82. offset: 0,
  83. color: color[4]
  84. }, {
  85. offset: 1,
  86. color: color[4]
  87. }])
  88. }
  89. }
  90. },
  91. {
  92. value: 0,
  93. name: '',
  94. tooltip: {
  95. show: false
  96. },
  97. },
  98. ]
  99. },
  100. ]
  101. };
  102. //第二个饼状图
  103. var optionCircleB = {
  104. backgroundColor: '#fff',
  105. title: {
  106. text: '15386',
  107. x: 'center',
  108. y: 'center',
  109. textStyle: {
  110. fontWeight: 'normal',
  111. fontSize: 14,
  112. color: "#94d96c",
  113. }
  114. },
  115. series: [{
  116. name: 'Line 1',
  117. type: 'pie',
  118. clockWise: false,
  119. radius: [37, 45],
  120. center:['50%','50%'],
  121. itemStyle: dataStyle,
  122. hoverAnimation: false,
  123. startAngle: 90,
  124. label:{
  125. borderRadius:'10',
  126. },
  127. data: [{
  128. value: 54.6,
  129. name: '外',
  130. itemStyle: {
  131. normal: {
  132. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
  133. offset: 0,
  134. color:color[2]
  135. }, {
  136. offset: 1,
  137. color: color[3]
  138. }])
  139. }
  140. }
  141. },
  142. {
  143. value: 0,
  144. name: '',
  145. tooltip: {
  146. show: false
  147. },
  148. },
  149. ]
  150. },
  151. {
  152. name: 'Line 2',
  153. type: 'pie',
  154. clockWise: false,
  155. radius: [30, 32],
  156. center:['50%','50%'],
  157. itemStyle: dataStyle,
  158. hoverAnimation: false,
  159. startAngle: 90,
  160. data: [{
  161. value: 56.7,
  162. name: '内',
  163. itemStyle: {
  164. normal: {
  165. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
  166. offset: 0,
  167. color: color[4]
  168. }, {
  169. offset: 1,
  170. color: color[4]
  171. }])
  172. }
  173. }
  174. },
  175. {
  176. value: 0,
  177. name: '',
  178. tooltip: {
  179. show: false
  180. },
  181. },
  182. ]
  183. },
  184. ]
  185. };
  186. myChart1.setOption(optionCircleA);
  187. myChart2.setOption(optionCircleB);
  188. }

效果如下图:

(2)使用echart创建人群密度线性图表,封装在函数lineSetA()内:

  1. //人群密度线性图表
  2. function lineSetA() {
  3. myChart3 = echarts.init(document.getElementById('ec3'));
  4. var colors = ['#12c3f8', '#4384d7'];
  5. optionLineA = {
  6. color: colors,
  7. visualMap: [{
  8. show: false,
  9. type: 'continuous',
  10. seriesIndex: 0,
  11. min: 0,
  12. max: 600,
  13. borderWidth: 3,
  14. color: colors,
  15. }],
  16. xAxis: {
  17. type: 'category',
  18. data: ['0', '2', '4', '6', '8', '10', '12', '14', '16', '18', '20', '22'],
  19. show: true,
  20. position: {
  21. bottom: 10,
  22. show: false,
  23. },
  24. onZero: false,
  25. axisLine: {
  26. lineStyle: {
  27. width: 0,
  28. }
  29. }
  30. },
  31. yAxis: {
  32. type: 'value',
  33. axisLabel: {
  34. formatter: '{value} 人',
  35. fontSize: 10,
  36. },
  37. axisLine: {
  38. lineStyle: {
  39. width: 0,
  40. }
  41. },
  42. minInterval: 300,
  43. },
  44. grid: [{
  45. top: '40',
  46. bottom: '25',
  47. left: '50',
  48. right: '10'
  49. }],
  50. series: [{
  51. data: [ 0, 10, 20, 30, 40, 100, 600, 900, 880, 900, 1100, 1000],
  52. type: 'line',
  53. smooth: true,
  54. markPoint: {
  55. data: [
  56. {
  57. name: '880',
  58. coord: ['16','880'],
  59. value: '880',
  60. ],
  61. label: {
  62. show: true,
  63. },
  64. }
  65. }]
  66. };
  67. myChart3.setOption(optionLineA);
  68. }

创建流量趋势线性图表,封装在函数lineSetB()内:

  1. //流量趋势线性图表
  2. function lineSetB() {
  3. myChart4 = echarts.init(document.getElementById('ec3'));
  4. var colors = ['#12c3f8', '#4384d7'];
  5. var optionLineB = {
  6. color: colors,
  7. visualMap: [{
  8. show: false,
  9. type: 'continuous',
  10. seriesIndex: 0,
  11. min: 0,
  12. max: 600,
  13. borderWidth: 3,
  14. color: colors,
  15. }],
  16. xAxis: {
  17. type: 'category',
  18. data: ['0', '2', '4', '6', '8', '10', '12', '14', '16', '18', '20', '22'],
  19. show: true,
  20. position: {
  21. bottom: 10,
  22. show: false,
  23. },
  24. onZero: false,
  25. axisLine: {
  26. lineStyle: {
  27. width: 0,
  28. }
  29. }
  30. },
  31. yAxis: {
  32. type: 'value',
  33. axisLabel: {
  34. formatter: '{value} 人/平方米',
  35. fontSize: 10,
  36. },
  37. axisLine: {
  38. lineStyle: {
  39. width: 0,
  40. }
  41. },
  42. minInterval: 0.5,
  43. },
  44. grid: [{
  45. top: '40',
  46. bottom: '25',
  47. left: '70',
  48. right: '10'
  49. }],
  50. series: [{
  51. data: [ 0, 1, 2, 3, 4, 3, 2, 3, 3.5, 2, 1, 3],
  52. type: 'line',
  53. smooth: true,
  54. markPoint: {
  55. data: [
  56. {
  57. name: '4',
  58. coord: ['14','3'],
  59. value: '4',
  60. }
  61. ],
  62. label: {
  63. show: true,
  64. },
  65. }
  66. }]
  67. };
  68. myChart4.setOption(optionLineB);
  69. }

切换线性图表数据显示实现:

  1. //切换线性图表数据显示
  2. $(".list-b .title-box .t-a").click(function() {//点击流量趋势
  3. $(".list-b .title-box .t-b").removeClass('active');//移除当前样式
  4. $(this).addClass('active');//给点击添加新样式
  5. resizeLineA();
  6. })
  7. $(".list-b .title-box .t-b").click(function() {//点击人群密度
  8. $(".list-b .title-box .t-a").removeClass('active');
  9. $(this).addClass('active');
  10. resizeLineA(1);
  11. })

更换装图表的盒子(div)和线性图表信息:

  1. function resizeLineA(n) {
  2. $(".line-cen").remove();//先移除原有的盒子
  3. var aa = document.createElement('div');//在创建一个新盒子装图表
  4. aa.id = 'ec3'
  5. aa.className = 'line-cen'
  6. $(".line-box").append(aa)
  7. if (n == 1) {
  8. lineSetA();//显示人群密度图表
  9. } else {
  10. lineSetB();//显示流量趋势图表
  11. }
  12. }

效果如下图:

除此之外,还可以根据实际情况再添加相应的图表。

2.check控制面板

  开发一个控制面板,对管理者来说可以更好的全局掌握控制商场情况我在控制面板上加了实时视频,全景漫游和客流分布,下面就这三个功能的实现过程做下介绍。

(1)客流分布热力图功能,以下载入的是模拟数据,投入使用后可直接载入实际数据,根据数据体现热力图的情况。

  1. //添加热力图,根据json文件
  2. function addHeatMap() {
  3. // 创建热力图对象
  4. if (!heatmapInstance)
  5. heatmapInstance = esmap.ESHeatMap.create(map, {
  6. radius: 24, //热点半径
  7. opacity: .5, //热力图透明度
  8. max: 35, //热力点value的最大值
  9. maxSize: 2048,
  10. gradient: {//渐变色值,可配
  11. 0.35: "green",
  12. 0.5: "yellow",
  13. 0.7: "orange",
  14. 0.85: "red"
  15. }
  16. $.getJSON("data/003.json", function(data) { //数据载入
  17. var datas = data.datas;
  18. var len = datas.length;
  19. exec(datas[0]["data"][0]["fnum"], datas[0]["data"][0]["points"]);//绘制热力图
  20. var index = 1;
  21. timer1 = setInterval(function () {
  22. if (index > 1) index = 0;
  23. for (var el of datas[0]["data"][0]["points"]) {
  24. switch (index) {
  25. case 0: el.value = el.value - 1;
  26. break;
  27. case 1: el.value = el.value + 1;
  28. break;
  29. }
  30. }
  31. exec(datas[0]["data"][0]["fnum"], datas[0]["data"][0]["points"]);
  32. index++;
  33. }, 2000)
  34. return;
  35. });
  36. function exec(fnum, points) {//绘制热力图函数
  37. var floorLayer = map.getFloor(fnum);//获取应用楼层
  38. heatmapInstance.clearPoints();//清除热力点
  39. heatmapInstance.addPoints(points);//热力点添加到热力图
  40. //热力图应用到哪一楼层
  41. floorLayer.applyHeatMap(heatmapInstance);
  42. }
  43. }

热力图如下:

(2)实时视频及全景漫游的实现:

  首先创建实时视频的摄像头图片标注和全景漫游的360°图片标注,标注实现后可在地图上点击相应的图片标注从而显示实时视频画面或360°全景画面,画面可拖拽可放大缩小。

各楼层实时视频的摄像头图片标注:

  1. //创建各楼层摄像头标注
  2. function showCameras() {
  3. var url = 'data/test666/model/camera1.js';
  4. //json数据,定义摄像头所在楼层和位置
  5. var infos = [{
  6. fnum: 1,
  7. cameras: [{
  8. x: 12683472.409512023,
  9. y: 2557870.1781415385,
  10. },
  11. {
  12. x: 12683450.258123305,
  13. y: 2557858.104209115
  14. },
  15. {
  16. x: 12683430.863774385,
  17. y: 2557865.8999765064
  18. }
  19. ]
  20. }, {
  21. fnum: 2,
  22. cameras: [{
  23. x: 12683472.409512023,
  24. y: 2557870.1781415385,
  25. },
  26. {
  27. x: 12683450.258123305,
  28. y: 2557858.104209115
  29. },
  30. {
  31. x: 12683430.863774385,
  32. y: 2557865.8999765064
  33. }
  34. ]
  35. }, {
  36. fnum: 3,
  37. cameras: [{
  38. x: 12683472.409512023,
  39. y: 2557870.1781415385,
  40. },
  41. {
  42. x: 12683450.258123305,
  43. y: 2557858.104209115
  44. },
  45. {
  46. x: 12683430.863774385,
  47. y: 2557865.8999765064
  48. }
  49. ]
  50. }];
  51. //创建三维模型标注 实时视频摄像头
  52. var ang = 0;
  53. infos.forEach(function (info) {
  54. var floorLayer = map.getFloor(info.fnum);
  55. var layer = floorLayer.getOrCreateLayerByName("cameras", esmap.ESLayerType.MODEL3D);
  56. var _id = 1;
  57. info.cameras.forEach(function (camera) {
  58. var im = new esmap.ES3DMarker({
  59. x: camera.x,
  60. y: camera.y,
  61. id: _id++,
  62. name: "camera",
  63. url: url,
  64. size: 44,
  65. angle: ang,
  66. height: 3,
  67. showLevel: 16,
  68. spritify: true
  69. });
  70. ang += 30;
  71. layer.addMarker(im);//一个楼层共用一个图层
  72. });
  73. layer && layer.show3D();
  74. });
  75. }

点击地图展示实时视频或全景漫游弹框(div)函数active():

  1. //地图点击标注后 临时创建div盒子 放全景图或实时视频
  2. function active(e, type) { // type: 1.pano; 0.video
  3. var cc = $($(".drag")[0]).clone();
  4. var wid = $(".drag").width();
  5.  
  6. $("body").append(cc);
  7. cc[0].style.display = "block";
  8. if (__xx < wid) {
  9. __xx = wid;
  10. }
  11. cc[0].style.left = (__xx - wid - 20).toString() + "px";
  12. cc[0].style.top = (__yy - 25 / 2).toString() + "px";
  13.  
  14. if (!type) {
  15. cc.find('.content')[0].innerHTML = '<video class="video_" src="videos/' + e.id_ + '.mp4" autoplay loop></video>';
  16. cc.find('.title h2')[0].innerHTML = '实时视频';
  17. createPopBox();
  18. } else {
  19. cc.find('.title h2')[0].innerHTML = '全景展示';
  20. var box = document.createElement('div');
  21. cc.find('.content').append(box);
  22. box.className = 'psv-box';
  23.  
  24. oPano = new CreatePanorama({
  25. container: box,
  26. panorama: 'image/pano/' + e.id + '/',
  27. six: 1
  28. })
  29. createPopBox(oPano);
  30. }
  31. }
  1. 展示的弹框可拖拽、大小可调整,功能实现如下函数:
  1. /*可拖拽可放大缩小弹框*/
  2. function createPopBox(pano) { // pano: 0.视频,1.全景
  3. /*-------------------------- +
  4. 获取id, class, tagName 函数
  5. +-------------------------- */
  6. var get = {
  7. byId: function (id) {
  8. return typeof id === "string" ? document.getElementById(id) : id;
  9. },
  10. byClass: function (sClass, oParent) {
  11. var aClass = [];
  12. var reClass = new RegExp("(^| )" + sClass + "( |$)");
  13. var aElem = this.byTagName("*", oParent);
  14. for (var i = 0; i < aElem.length; i++) reClass.test(aElem[i].className) && aClass.push(aElem[i]);
  15. return aClass
  16. },
  17. byTagName: function (elem, obj) {
  18. return (obj || document).getElementsByTagName(elem);
  19. }
  20. };
  21. var dragMinWidth = 250;
  22. var dragMinHeight = 173;
  23. /*-------------------------- +
  24. 拖拽函数
  25. +-------------------------- */
  26. function drag(oDrag, handle) {
  27. var disX = dixY = 0;
  28. var oMax = get.byClass("max", oDrag)[0];//获取最大化div的 class
  29. var oRevert = get.byClass("revert", oDrag)[0];//获取恢复div的 class
  30. var oClose = get.byClass("close", oDrag)[0];//获取关闭div的 class
  31. handle = handle || oDrag;
  32. handle.style.cursor = "move";
  33. handle.onmousedown = function (event) {
  34. var event = event || window.event;
  35. disX = event.clientX - oDrag.offsetLeft;
  36. disY = event.clientY - oDrag.offsetTop;
  37.  
  38. document.onmousemove = function (event) {
  39. var event = event || window.event;
  40. var iL = event.clientX - disX;
  41. var iT = event.clientY - disY;
  42. var maxL = document.documentElement.clientWidth - oDrag.offsetWidth;
  43. var maxT = document.documentElement.clientHeight - oDrag.offsetHeight;
  44.  
  45. iL <= 0 && (iL = 0);
  46. iT <= 0 && (iT = 0);
  47. iL >= maxL && (iL = maxL);
  48. iT >= maxT && (iT = maxT);
  49.  
  50. oDrag.style.left = iL + "px";
  51. oDrag.style.top = iT + "px";
  52.  
  53. return false
  54. };
  55. document.onmouseup = function () {
  56. document.onmousemove = null;
  57. document.onmouseup = null;
  58. this.releaseCapture && this.releaseCapture()
  59. };
  60. this.setCapture && this.setCapture();
  61. return false
  62. };
  63. //最大化按钮
  64. oMax.onclick = function () {
  65. if (!pano) {
  66. $(this).parents('.drag').find('.video_')[0].webkitEnterFullscreen(true);
  67. } else {
  68. fullPano = 1;
  69. oDrag.classList.add('over-auto');
  70. var _box = $(oDrag).find('.psv-box')[0];
  71.  
  72. _box.classList.add('psv-full', 'over-auto');
  73.  
  74. var _div = document.createElement('div');
  75. document.body.append(_div);
  76. _div.className = 'psv-full-btns';
  77. _div.innerHTML = '<a class="full-revert" href="javascript:;" title="还原"></a>'
  78.  
  79. document.onkeydown = function (e) {
  80. if (e.keyCode == 27 && fullPano == 1) {
  81. fullPano = 0;
  82. oDrag.classList.remove('over-auto');
  83. _box.classList.remove('psv-full', 'over-auto');
  84. _div.remove();
  85. pano.onWindowResize();
  86. }
  87. }
  88.  
  89. $(_div).find('.full-revert').click(function () {
  90. fullPano = 0;
  91. oDrag.classList.remove('over-auto');
  92. _box.classList.remove('psv-full', 'over-auto');
  93. _div.remove();
  94. pano.onWindowResize();
  95. })
  96.  
  97. pano.onWindowResize();
  98. }
  99. };
  100. //还原按钮
  101. oRevert.onclick = function () {
  102. oDrag.style.width = dragMinWidth + "px";
  103. oDrag.style.height = dragMinHeight + "px";
  104. oDrag.style.left = (document.documentElement.clientWidth - oDrag.offsetWidth) / 2 + "px";
  105. oDrag.style.top = (document.documentElement.clientHeight - oDrag.offsetHeight) / 2 + "px";
  106. this.style.display = "none";
  107. oMax.style.display = "block";
  108. pano && pano.onWindowResize();
  109. };
  110. //关闭按钮
  111. oClose.onclick = function () {
  112. if (!pano) {
  113. $(this).parents('.drag').remove();
  114. } else {
  115. oPano = null;
  116. $(this).parents('.drag').remove();
  117. }
  118. };
  119. //阻止冒泡
  120. oMax.onmousedown = oClose.onmousedown = function (event) {
  121. this.onfocus = function () {
  122. this.blur();
  123. };
  124. (event || window.event).cancelBubble = true
  125. };
  126. }
  127. /*-------------------------- +
  128. 改变大小函数
  129. +-------------------------- */
  130. function resize(oParent, handle, isLeft, isTop, lockX, lockY) {
  131. handle.onmousedown = function (event) {
  132. var event = event || window.event;
  133. var disX = event.clientX - handle.offsetLeft;
  134. var disY = event.clientY - handle.offsetTop;
  135. var iParentTop = oParent.offsetTop;
  136. var iParentLeft = oParent.offsetLeft;
  137. var iParentWidth = oParent.offsetWidth;
  138. var iParentHeight = oParent.offsetHeight;
  139.  
  140. document.onmousemove = function (event) {
  141. var event = event || window.event;
  142.  
  143. var iL = event.clientX - disX;
  144. var iT = event.clientY - disY;
  145. var maxW = document.documentElement.clientWidth - oParent.offsetLeft - 2;
  146. var maxH = document.documentElement.clientHeight - oParent.offsetTop - 2;
  147. var iW = isLeft ? iParentWidth - iL : handle.offsetWidth + iL;
  148. var iH = isTop ? iParentHeight - iT : handle.offsetHeight + iT;
  149.  
  150. isLeft && (oParent.style.left = iParentLeft + iL + "px");
  151. isTop && (oParent.style.top = iParentTop + iT + "px");
  152.  
  153. iW < dragMinWidth && (iW = dragMinWidth);
  154. iW > maxW && (iW = maxW);
  155. lockX || (oParent.style.width = iW + "px");
  156.  
  157. iH < dragMinHeight && (iH = dragMinHeight);
  158. iH > maxH && (iH = maxH);
  159. lockY || (oParent.style.height = iH + "px");
  160.  
  161. if ((isLeft && iW == dragMinWidth) || (isTop && iH == dragMinHeight)) document.onmousemove = null;
  162.  
  163. pano && pano.onWindowResize();
  164.  
  165. return false;
  166. };
  167. document.onmouseup = function () {
  168. document.onmousemove = null;
  169. document.onmouseup = null;
  170. };
  171. return false;
  172. }
  173. };
  174. function aaa() {
  175. var dom = document.getElementsByClassName("drag");
  176. var oDrag = dom[dom.length - 1];
  177.  
  178. var oTitle = get.byClass("title", oDrag)[0];
  179. var oL = get.byClass("resizeL", oDrag)[0];
  180. var oT = get.byClass("resizeT", oDrag)[0];
  181. var oR = get.byClass("resizeR", oDrag)[0];
  182. var oB = get.byClass("resizeB", oDrag)[0];
  183.  
  184. var oLT = get.byClass("resizeLT", oDrag)[0];
  185. var oTR = get.byClass("resizeTR", oDrag)[0];
  186. var oBR = get.byClass("resizeBR", oDrag)[0];
  187. var oLB = get.byClass("resizeLB", oDrag)[0];
  188.  
  189. drag(oDrag, oTitle);
  190. //拉四角
  191. resize(oDrag, oLT, true, true, false, false);
  192. resize(oDrag, oTR, false, true, false, false);
  193. resize(oDrag, oBR, false, false, false, false);
  194. resize(oDrag, oLB, true, false, false, false);
  195. //拉四边
  196. resize(oDrag, oL, true, false, false, true);
  197. resize(oDrag, oT, false, true, true, false);
  198. resize(oDrag, oR, false, false, false, true);
  199. resize(oDrag, oB, false, false, true, false);
  200.  
  201. oDrag.style.left = (document.documentElement.clientWidth - oDrag.offsetWidth) / 2 + "px";
  202. oDrag.style.top = (document.documentElement.clientHeight - oDrag.offsetHeight) / 2 + "px";
  203. }
  204. aaa();
  205. }

功能都实现后投入使用,点击地图上的标注显示相应的实时视频,这里我使用了ESMap的地图点击事件map.on(“mapClickNode”,function(){});

  1. map.on("mapClickNode", function (e) {
  2. removeAll();
  3. if (e.nodeType && e.nodeType == 31 && e.name && e.name == 'myMarker') {//全景
  4. active(e, 1);
  5. }
  6. if (e.nodeType && e.nodeType == 6 && e.name && e.name == 'camera') {//视频
  7. active(e)
  8. }
  9. if (e.nodeType && e.nodeType == 5) {//点击地图商铺显示相应运营情况
  10. if (e.name) {
  11. var obj = {
  12. id: e.ID,
  13. fnum: e.FloorNum,
  14. x: e.x,
  15. y: e.y,
  16. name: e.name
  17. }
  18. searchClick(obj);// 函数如下
  1. } } })

封装气泡标注函数searchClick(),显示商铺信息:

  1. function searchClick(data, isAddImageMarker) {
  2. if (!data.name) return;
  3. // 添加pop
  4. removeAll();
  5. var floorLayer = map.getFloor(data.fnum);
  6.  
  7. if (isAddImageMarker) {
  8. floorControl.changeFocusFloor(data.fnum);
  9. }
  10. if (data.name == '房间') {
  11. var dom = '<div class="pop-content"><strong>房间 ' + data.id + '</strong><p>经度:' + data.x.toFixed(3) + '</p><p>纬度:' + data.y.toFixed(3) + '</p></div>';
  12. } else {
  13. var shopDatas = getShopMsg(data.id);//数字number
  14. var dom = '<div class="pop-content"><strong>' + data.name + '</strong><p>人流量:' + shopDatas.msgA + '</p><p>营业额:' + shopDatas.msgB + '</p></div>'
  15. }
  16. //添加信息窗
  17. popMarker = new esmap.ESPopMarker({
  18. mapCoord: {
  19. //设置弹框的x轴
  20. x: data.x,
  21. //设置弹框的y轴
  22. y: data.y,
  23. height: 1, //控制信息窗的高度
  24. //设置弹框位于的楼层
  25. fnum: data.fnum
  26. },
  27. //设置弹框的宽度
  28. width: 200,
  29. //设置弹框的高度
  30. height: 120,
  31. marginTop: 10,
  32. //设置弹框的内容
  33. content: dom,
  34. // content: '<input id="pop-input" type="text"/>',
  35. closeCallBack: function () {
  36. //信息窗点击关闭操作
  37. // alert('信息窗关闭了!');
  38. },
  39. });
  40. $(".es-control-popmarker input").val('✖'); // 手动添加close按钮value
  41. }

效果图如下:

各楼层全景漫游的360°图片标注:

  1. //创建360°图片标注到各层
  2. function showImageMarker() {
  3. var _arr = [{
  4. fnum: 1,
  5. node: [{
  6. x: 12683473.823037906,
  7. y: 2557891.805802924,
  8. },
  9. {
  10. x: 12683424.1333389,
  11. y: 2557880.7494297,
  12. }
  13. ]
  14. }, {
  15. fnum: 2,
  16. node: [{
  17. x: 12683473.823037906,
  18. y: 2557891.805802924,
  19. },
  20. {
  21. x: 12683424.1333389,
  22. y: 2557880.7494297,
  23. }
  24. ]
  25. }, {
  26. fnum: 3,
  27. node: [{
  28. x: 12683473.823037906,
  29. y: 2557891.805802924,
  30. },
  31. {
  32. x: 12683424.1333389,
  33. y: 2557880.7494297,
  34. }
  35. ]
  36. }]
  37. for (var el of _arr) {
  38. var floorLayer = map.getFloor(el.fnum);
  39. var im_layer = new esmap.ESLayer('imageMarker');//创建图层
  40. im_layer.name = 'mylayer';//给图层命名
  41. var index = 1;
  42. for (var el2 of el.node) {
  43. var im = new esmap.ESImageMarker({
  44. x: el2.x,
  45. y: el2.y,
  46. url: 'image/360.png',
  47. id: index++,
  48. size: 50,
  49. name: 'myMarker',
  50. zoom: 2,
  51. })
  52. im_layer.addMarker(im);
  53. floorLayer.addLayer(im_layer);
  54. }
  55. }
  56. }

图片标注创建后,使用three.JS创建全景图,然后绑定鼠标事件

  1. function CreatePanorama(prop) {
  2. var camera, scene, renderer, container, mesh;
  3. var texture_placeholder,
  4. target = new THREE.Vector3();//创建3维向量
  5.  
  6. this.container = container;
  7. this.panorama = prop.panorama;
  8. this.camera = camera;
  9. this.scene = scene;
  10. this.renderer = renderer;
  11. this.mesh = mesh;
  12.  
  13. function init() {
  14. container = prop.container;
  15. camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 1, 1100);
  16. scene = new THREE.Scene();
  17. texture_placeholder = document.createElement('canvas');
  18. texture_placeholder.width = 128;
  19. texture_placeholder.height = 128;
  20.  
  21. var context = texture_placeholder.getContext('2d');
  22. context.fillStyle = 'rgb( 200, 200, 200 )';
  23. context.fillRect(0, 0, texture_placeholder.width, texture_placeholder.height);
  24.  
  25. if (prop.six) {
  26. var materials = [
  27. loadTexture(prop.panorama + 'r.jpg'), // right
  28. loadTexture(prop.panorama + 'l.jpg'), // left
  29. loadTexture(prop.panorama + 'u.jpg'), // top
  30. loadTexture(prop.panorama + 'd.jpg'), // bottom
  31. loadTexture(prop.panorama + 'b.jpg'), // back
  32. loadTexture(prop.panorama + 'f.jpg') // front
  33. ];
  34. var matss = new THREE.MultiMaterial(materials)
  35. mesh = new THREE.Mesh(new THREE.BoxGeometry(300, 300, 300, 7, 7, 7), matss);
  36. } else {
  37. var geometry = new THREE.SphereGeometry(100, 64, 64, -1.5707963267948966);
  38. var material = new THREE.MeshBasicMaterial({
  39. map: new THREE.TextureLoader().load(prop.panorama),
  40. })
  41. mesh = new THREE.Mesh(geometry, material);
  42. }
  43. mesh.scale.x = -1;
  44. scene.add(mesh);
  45.  
  46. renderer = new THREE.WebGLRenderer({//创建一个webGL渲染器,renderer
  47. antialias: true
  48. })
  49. renderer.setPixelRatio(window.devicePixelRatio);//设备设置像素比
  50. renderer.setSize(container.clientWidth, container.clientHeight);//调整输出canvas尺寸
  51.  
  52. container.appendChild(renderer.domElement);
  53. //监听鼠标各种事件
  54.    container.addEventListener('mousedown', onDocumentMouseDown, false);
  55. container.addEventListener('mousemove', onDocumentMouseMove, false);
  56. container.addEventListener('mouseup', onDocumentMouseUp, false);
  57. container.addEventListener('wheel', onDocumentMouseWheel, false);
  58.  
  59. container.addEventListener('touchstart', onDocumentTouchStart, false);
  60. container.addEventListener('touchmove', onDocumentTouchMove, false);
  61.  
  62. window.addEventListener('resize', onWindowResize, false);
  63. }
  64. }

全景漫游完成,在地图上点击、拖拽、缩放功能如实时视频一样,效果如下图:

3.创建一个搜索框,可以直接锁定目标,查看店铺运营情况

  1. // 通过店名搜索地图中店铺 搜索框函数
  2. function searchByName(name) {
  3. Listmodel.item = [];
  4. if (!name) return;
  5. var data = map.mapService.sourceData.floors;//获取地图信息
  6. for (var ele of data) {//遍历获取到的信息
  7. for (var i in ele.Rooms) { //遍历获取到数组里的Rooms
  8. var el = ele.Rooms[i];
  9. if (el.name) {
  10. var a = el.name.indexOf(name);//查找输入的店名是否在地图内存在
  11. if (a != -1) {//如果存在
  12. var obj = {
  13. x: el.CenX,
  14. y: el.CenY,
  15. id: el._id,
  16. fnum: ele.floornum,
  17. name: el.name
  18. }
  19. Listmodel.item.push(obj);//把输入的店名信息存入数组
  20. }
  21. }
  22. }
  23. }
  24. }

效果如下图:

  以上就是我就商场管理者在管理过程中所面临的一些问题,开发的商场管理系统的一个界面,当然我只是简单实现了一些功能,在实际开发过程可根据实际情况定制一些功能方案,从而达到管理者高效管理的目的。ESMap-SDK提供的免费地图开发和热力图、图片标注等功能实现的支持。

Thank you for reading!

使用JS+Three.js+Echart开发商场室内地图客流信息统计功能的更多相关文章

  1. 如何使用JS来开发室内地图商场停车场车位管理系统

    在线体验到室内地图的功能后,手机对室内地图加载一个字,要显示“快”,目前微信和电脑都可以打开室内地图的要求是3秒内打开,能有定位导航的功能最好,这样方便找到要去的地方. 对于经常逛商场的MM来说,哪里 ...

  2. 支付宝小程序室内地图导航开发-支付宝小程序JS加载esmap地图

    如果是微信小程序开发,请参考微信小程序室内地图导航开发-微信小程序JS加载esmap地图文章 一.在支付宝小程序里显示室内三维地图 需要满足的两个条件 调用ESMap室内地图需要用到小程序web-vi ...

  3. 前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

    一.ES6语法 ES6中对数组新增了几个函数:map().filter().reduce() ES5新增的forEach(). 都是一些语法糖. 1.1 forEach()遍历数组 forEach() ...

  4. 微信小程序室内地图导航开发-微信小程序JS加载esmap地图

    一.在微信小程序里显示室内三维地图 需要满足的两个条件 调用ESMap室内地图需要用到小程序web-view组件,想要通过 web-view 调用ESMap室内地图需要满足以下 2 个条件: 1. 小 ...

  5. 《Node.js+MongoDB+AngularJS Web开发》读书笔记及联想

    总体介绍 <Node.js+MongoDB+AngularJS Web开发>,于2015年6月出版,是一本翻译过来的书,原书名为<Node.js,MongoDB and Angula ...

  6. 使用backbone.js、zepto.js和trigger.io开发HTML5 App

    为了力求运行速度快.响应迅即,我们推荐使用backbone.js和zepto.js. 为了让这个过程更有意思,我们开发了一个小小的示例项目,使用CSS重置样式.Backbone.js和带转场效果的几个 ...

  7. 《.NET最佳实践》与Ext JS/Touch的团队开发

    概述 持续集成 编码规范 测试 小结 概述 有不少开发人员都问过我,Ext JS/Touch是否支持团队开发?对于这个问题,我可以毫不犹豫的回答:支持.原因是在Sencha官网博客中客户示例中,有不少 ...

  8. arcgis api 4.x for js 结合 react 入门开发系列初探篇(附源码下载)

    你还在使用 JQuery 或者 Dojo 框架开发 arcgis api 4.x for js 吗?想试试模块化开发吗?随着前端技术的发展,arcgis api 4.x for js 也有了结合 re ...

  9. 转:arcgis api for js入门开发系列四地图查询

    原文地址:arcgis api for js入门开发系列四地图查询 arcgis for js的地图查询方式,一般来说,总共有三种查询方式:FindTask.IdentifyTask.QueryTas ...

随机推荐

  1. java动态绑定与静态绑定【转】

    程序绑定的概念: 绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来.对java来说,绑定分为静态绑定和动态绑定:或者叫做前期绑定和后期绑定.静态绑定: 在程序执行前方法已经被绑定(也就是说在 ...

  2. C语言可变参数va_list

    一.什么是可变参数 在C语言编程中有时会遇到一些参数个数可变的函数,例如printf(),scanf()函数,其函数原型为: int printf(const char* format,-) int ...

  3. PAT1035: Password

    1035. Password (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue To prepare f ...

  4. ehcache与redis的比较与应用场景分析(转)

    ehcache直接在jvm虚拟机中缓存,速度快,效率高:但是缓存共享麻烦,集群分布式应用不方便.redis是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,处理集群和分布式缓存 ...

  5. Socket Connect问题

    一.非阻塞Connect对于Select时应注意的问题二.linux客户端socket非阻塞connect编程 一.非阻塞Connect对于Select时应注意的问题 对于面向连接的socket(SO ...

  6. vue简介

    vue的介绍 vue官网说:Vue.js(读音 /vjuː/,类似于 view) 是一套构建用户界面的渐进式框架.与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计. vue的优点 1.易用 ...

  7. 树莓派.Raspberry Pi 3碰到"Unable to determine hardware version. I see: Hardware : BCM2835"错误的解决过程

    按pi4jp官方的安装指导(http://pi4j.com/install.html)进行安装 curl -s get.pi4j.com | sudo bash 安装完成后执行JAVA程序, 发现如下 ...

  8. proxy.go

    )     for {         select {         case <-otherSwitch:             complete <- true          ...

  9. MFC学习笔记_关于CSpinButtonCtrl

    CSpinButtonCtrl使用起来比较特殊,使用起来,需要注意一些地方.实际的教程也比较少.为了让后人少走弯路,这里写这篇文章以说明.1.添加EDIT控件2.添加Spin控件如果不是这样的顺序的话 ...

  10. bzoj3812&uoj37 主旋律

    正着做不好做,于是我们考虑反着来,如何计算一个点集s的答案呢,一定是所有的方案减去不合法的方案,不合法的方案一定是缩完点后是一个DAG,那么就一定有度数为0的scc,于是我们枚举s的子集,就是说这些点 ...