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

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

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

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

开发流程如下:

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

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

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

function circleSet() {
myChart1 = echarts.init(document.getElementById('ec1'));
myChart2 = echarts.init(document.getElementById('ec2'));
var color= ['#b679fe', '#6271fd','#94d96c', '#0fbdd9','#f0f0f0'];
var dataStyle = {
normal: {
label: {
show: false
},
labelLine: {
show: false
},
shadowBlur: 40,
borderWidth: 10,
shadowColor: 'rgba(0, 0, 0, 0)' //边框阴影
}
};
//第一个饼状图
var optionCircleA = {
backgroundColor: '#fff',
title: {
text: '52452',
x: 'center',
y: 'center',
textStyle: {
fontWeight: 'normal',
fontSize: 14,
color: "#b679fe",
}
},
series: [{
name: 'Line 1',
type: 'pie',
clockWise: false,
radius: [37, 45],
center:['50%','50%'],
itemStyle: dataStyle,
hoverAnimation: false,
startAngle: 90,
label:{
borderRadius:'10',
},
data: [{
value: 54.6,
name: '外',
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color:color[0]
}, {
offset: 1,
color: color[1]
}])
}
}
},
{
value: 0,
name: '',
tooltip: {
show: false
},
},
]
},
{
name: 'Line 2',
type: 'pie',
clockWise: false,
radius: [30, 32],
center:['50%','50%'],
itemStyle: dataStyle,
hoverAnimation: false,
startAngle: 90,
data: [{
value: 56.7,
name: '内',
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: color[4]
}, {
offset: 1,
color: color[4]
}])
}
}
},
{
value: 0,
name: '',
tooltip: {
show: false
},
},
]
},
]
};
//第二个饼状图
var optionCircleB = {
backgroundColor: '#fff',
title: {
text: '15386',
x: 'center',
y: 'center',
textStyle: {
fontWeight: 'normal',
fontSize: 14,
color: "#94d96c",
}
},
series: [{
name: 'Line 1',
type: 'pie',
clockWise: false,
radius: [37, 45],
center:['50%','50%'],
itemStyle: dataStyle,
hoverAnimation: false,
startAngle: 90,
label:{
borderRadius:'10',
},
data: [{
value: 54.6,
name: '外',
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color:color[2]
}, {
offset: 1,
color: color[3]
}])
}
}
},
{
value: 0,
name: '',
tooltip: {
show: false
},
},
]
},
{
name: 'Line 2',
type: 'pie',
clockWise: false,
radius: [30, 32],
center:['50%','50%'],
itemStyle: dataStyle,
hoverAnimation: false,
startAngle: 90,
data: [{
value: 56.7,
name: '内',
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: color[4]
}, {
offset: 1,
color: color[4]
}])
}
}
},
{
value: 0,
name: '',
tooltip: {
show: false
},
},
]
},
]
};
myChart1.setOption(optionCircleA);
myChart2.setOption(optionCircleB);
}

效果如下图:

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

    //人群密度线性图表
function lineSetA() {
myChart3 = echarts.init(document.getElementById('ec3'));
var colors = ['#12c3f8', '#4384d7'];
optionLineA = {
color: colors,
visualMap: [{
show: false,
type: 'continuous',
seriesIndex: 0,
min: 0,
max: 600,
borderWidth: 3,
color: colors,
}],
xAxis: {
type: 'category',
data: ['0', '2', '4', '6', '8', '10', '12', '14', '16', '18', '20', '22'],
show: true,
position: {
bottom: 10,
show: false,
},
onZero: false,
axisLine: {
lineStyle: {
width: 0,
}
}
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value} 人',
fontSize: 10,
},
axisLine: {
lineStyle: {
width: 0,
}
},
minInterval: 300,
},
grid: [{
top: '40',
bottom: '25',
left: '50',
right: '10'
}],
series: [{
data: [ 0, 10, 20, 30, 40, 100, 600, 900, 880, 900, 1100, 1000],
type: 'line',
smooth: true,
markPoint: {
data: [
{
name: '880',
coord: ['16','880'],
value: '880',
],
label: {
show: true,
},
}
}]
};
myChart3.setOption(optionLineA);
}

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

//流量趋势线性图表
function lineSetB() {
myChart4 = echarts.init(document.getElementById('ec3'));
var colors = ['#12c3f8', '#4384d7'];
var optionLineB = {
color: colors,
visualMap: [{
show: false,
type: 'continuous',
seriesIndex: 0,
min: 0,
max: 600,
borderWidth: 3,
color: colors,
}],
xAxis: {
type: 'category',
data: ['0', '2', '4', '6', '8', '10', '12', '14', '16', '18', '20', '22'],
show: true,
position: {
bottom: 10,
show: false,
},
onZero: false,
axisLine: {
lineStyle: {
width: 0,
}
}
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value} 人/平方米',
fontSize: 10,
},
axisLine: {
lineStyle: {
width: 0,
}
},
minInterval: 0.5,
},
grid: [{
top: '40',
bottom: '25',
left: '70',
right: '10'
}],
series: [{
data: [ 0, 1, 2, 3, 4, 3, 2, 3, 3.5, 2, 1, 3],
type: 'line',
smooth: true,
markPoint: {
data: [
{
name: '4',
coord: ['14','3'],
value: '4',
}
],
label: {
show: true,
},
}
}]
};
myChart4.setOption(optionLineB);
}

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

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

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

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

效果如下图:

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

2.check控制面板

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

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

//添加热力图,根据json文件
function addHeatMap() {
// 创建热力图对象
if (!heatmapInstance)
heatmapInstance = esmap.ESHeatMap.create(map, {
radius: 24, //热点半径
opacity: .5, //热力图透明度
max: 35, //热力点value的最大值
maxSize: 2048,
gradient: {//渐变色值,可配
0.35: "green",
0.5: "yellow",
0.7: "orange",
0.85: "red"
}
$.getJSON("data/003.json", function(data) { //数据载入
var datas = data.datas;
var len = datas.length;
exec(datas[0]["data"][0]["fnum"], datas[0]["data"][0]["points"]);//绘制热力图
var index = 1;
timer1 = setInterval(function () {
if (index > 1) index = 0;
for (var el of datas[0]["data"][0]["points"]) {
switch (index) {
case 0: el.value = el.value - 1;
break;
case 1: el.value = el.value + 1;
break;
}
}
exec(datas[0]["data"][0]["fnum"], datas[0]["data"][0]["points"]);
index++;
}, 2000)
return;
});
function exec(fnum, points) {//绘制热力图函数
var floorLayer = map.getFloor(fnum);//获取应用楼层
heatmapInstance.clearPoints();//清除热力点
heatmapInstance.addPoints(points);//热力点添加到热力图
//热力图应用到哪一楼层
floorLayer.applyHeatMap(heatmapInstance);
}
}

热力图如下:

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

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

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

//创建各楼层摄像头标注
function showCameras() {
var url = 'data/test666/model/camera1.js';
//json数据,定义摄像头所在楼层和位置
var infos = [{
fnum: 1,
cameras: [{
x: 12683472.409512023,
y: 2557870.1781415385,
},
{
x: 12683450.258123305,
y: 2557858.104209115
},
{
x: 12683430.863774385,
y: 2557865.8999765064
}
]
}, {
fnum: 2,
cameras: [{
x: 12683472.409512023,
y: 2557870.1781415385,
},
{
x: 12683450.258123305,
y: 2557858.104209115
},
{
x: 12683430.863774385,
y: 2557865.8999765064
}
]
}, {
fnum: 3,
cameras: [{
x: 12683472.409512023,
y: 2557870.1781415385,
},
{
x: 12683450.258123305,
y: 2557858.104209115
},
{
x: 12683430.863774385,
y: 2557865.8999765064
}
]
}];
//创建三维模型标注 实时视频摄像头
var ang = 0;
infos.forEach(function (info) {
var floorLayer = map.getFloor(info.fnum);
var layer = floorLayer.getOrCreateLayerByName("cameras", esmap.ESLayerType.MODEL3D);
var _id = 1;
info.cameras.forEach(function (camera) {
var im = new esmap.ES3DMarker({
x: camera.x,
y: camera.y,
id: _id++,
name: "camera",
url: url,
size: 44,
angle: ang,
height: 3,
showLevel: 16,
spritify: true
});
ang += 30;
layer.addMarker(im);//一个楼层共用一个图层
});
layer && layer.show3D();
});
}

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

//地图点击标注后  临时创建div盒子  放全景图或实时视频
function active(e, type) { // type: 1.pano; 0.video
var cc = $($(".drag")[0]).clone();
var wid = $(".drag").width(); $("body").append(cc);
cc[0].style.display = "block";
if (__xx < wid) {
__xx = wid;
}
cc[0].style.left = (__xx - wid - 20).toString() + "px";
cc[0].style.top = (__yy - 25 / 2).toString() + "px"; if (!type) {
cc.find('.content')[0].innerHTML = '<video class="video_" src="videos/' + e.id_ + '.mp4" autoplay loop></video>';
cc.find('.title h2')[0].innerHTML = '实时视频';
createPopBox();
} else {
cc.find('.title h2')[0].innerHTML = '全景展示';
var box = document.createElement('div');
cc.find('.content').append(box);
box.className = 'psv-box'; oPano = new CreatePanorama({
container: box,
panorama: 'image/pano/' + e.id + '/',
six: 1
})
createPopBox(oPano);
}
}
展示的弹框可拖拽、大小可调整,功能实现如下函数:
/*可拖拽可放大缩小弹框*/
function createPopBox(pano) { // pano: 0.视频,1.全景
/*-------------------------- +
获取id, class, tagName 函数
+-------------------------- */
var get = {
byId: function (id) {
return typeof id === "string" ? document.getElementById(id) : id;
},
byClass: function (sClass, oParent) {
var aClass = [];
var reClass = new RegExp("(^| )" + sClass + "( |$)");
var aElem = this.byTagName("*", oParent);
for (var i = 0; i < aElem.length; i++) reClass.test(aElem[i].className) && aClass.push(aElem[i]);
return aClass
},
byTagName: function (elem, obj) {
return (obj || document).getElementsByTagName(elem);
}
};
var dragMinWidth = 250;
var dragMinHeight = 173;
/*-------------------------- +
拖拽函数
+-------------------------- */
function drag(oDrag, handle) {
var disX = dixY = 0;
var oMax = get.byClass("max", oDrag)[0];//获取最大化div的 class
var oRevert = get.byClass("revert", oDrag)[0];//获取恢复div的 class
var oClose = get.byClass("close", oDrag)[0];//获取关闭div的 class
handle = handle || oDrag;
handle.style.cursor = "move";
handle.onmousedown = function (event) {
var event = event || window.event;
disX = event.clientX - oDrag.offsetLeft;
disY = event.clientY - oDrag.offsetTop; document.onmousemove = function (event) {
var event = event || window.event;
var iL = event.clientX - disX;
var iT = event.clientY - disY;
var maxL = document.documentElement.clientWidth - oDrag.offsetWidth;
var maxT = document.documentElement.clientHeight - oDrag.offsetHeight; iL <= 0 && (iL = 0);
iT <= 0 && (iT = 0);
iL >= maxL && (iL = maxL);
iT >= maxT && (iT = maxT); oDrag.style.left = iL + "px";
oDrag.style.top = iT + "px"; return false
};
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
this.releaseCapture && this.releaseCapture()
};
this.setCapture && this.setCapture();
return false
};
//最大化按钮
oMax.onclick = function () {
if (!pano) {
$(this).parents('.drag').find('.video_')[0].webkitEnterFullscreen(true);
} else {
fullPano = 1;
oDrag.classList.add('over-auto');
var _box = $(oDrag).find('.psv-box')[0]; _box.classList.add('psv-full', 'over-auto'); var _div = document.createElement('div');
document.body.append(_div);
_div.className = 'psv-full-btns';
_div.innerHTML = '<a class="full-revert" href="javascript:;" title="还原"></a>' document.onkeydown = function (e) {
if (e.keyCode == 27 && fullPano == 1) {
fullPano = 0;
oDrag.classList.remove('over-auto');
_box.classList.remove('psv-full', 'over-auto');
_div.remove();
pano.onWindowResize();
}
} $(_div).find('.full-revert').click(function () {
fullPano = 0;
oDrag.classList.remove('over-auto');
_box.classList.remove('psv-full', 'over-auto');
_div.remove();
pano.onWindowResize();
}) pano.onWindowResize();
}
};
//还原按钮
oRevert.onclick = function () {
oDrag.style.width = dragMinWidth + "px";
oDrag.style.height = dragMinHeight + "px";
oDrag.style.left = (document.documentElement.clientWidth - oDrag.offsetWidth) / 2 + "px";
oDrag.style.top = (document.documentElement.clientHeight - oDrag.offsetHeight) / 2 + "px";
this.style.display = "none";
oMax.style.display = "block";
pano && pano.onWindowResize();
};
//关闭按钮
oClose.onclick = function () {
if (!pano) {
$(this).parents('.drag').remove();
} else {
oPano = null;
$(this).parents('.drag').remove();
}
};
//阻止冒泡
oMax.onmousedown = oClose.onmousedown = function (event) {
this.onfocus = function () {
this.blur();
};
(event || window.event).cancelBubble = true
};
}
/*-------------------------- +
改变大小函数
+-------------------------- */
function resize(oParent, handle, isLeft, isTop, lockX, lockY) {
handle.onmousedown = function (event) {
var event = event || window.event;
var disX = event.clientX - handle.offsetLeft;
var disY = event.clientY - handle.offsetTop;
var iParentTop = oParent.offsetTop;
var iParentLeft = oParent.offsetLeft;
var iParentWidth = oParent.offsetWidth;
var iParentHeight = oParent.offsetHeight; document.onmousemove = function (event) {
var event = event || window.event; var iL = event.clientX - disX;
var iT = event.clientY - disY;
var maxW = document.documentElement.clientWidth - oParent.offsetLeft - 2;
var maxH = document.documentElement.clientHeight - oParent.offsetTop - 2;
var iW = isLeft ? iParentWidth - iL : handle.offsetWidth + iL;
var iH = isTop ? iParentHeight - iT : handle.offsetHeight + iT; isLeft && (oParent.style.left = iParentLeft + iL + "px");
isTop && (oParent.style.top = iParentTop + iT + "px"); iW < dragMinWidth && (iW = dragMinWidth);
iW > maxW && (iW = maxW);
lockX || (oParent.style.width = iW + "px"); iH < dragMinHeight && (iH = dragMinHeight);
iH > maxH && (iH = maxH);
lockY || (oParent.style.height = iH + "px"); if ((isLeft && iW == dragMinWidth) || (isTop && iH == dragMinHeight)) document.onmousemove = null; pano && pano.onWindowResize(); return false;
};
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
};
return false;
}
};
function aaa() {
var dom = document.getElementsByClassName("drag");
var oDrag = dom[dom.length - 1]; var oTitle = get.byClass("title", oDrag)[0];
var oL = get.byClass("resizeL", oDrag)[0];
var oT = get.byClass("resizeT", oDrag)[0];
var oR = get.byClass("resizeR", oDrag)[0];
var oB = get.byClass("resizeB", oDrag)[0]; var oLT = get.byClass("resizeLT", oDrag)[0];
var oTR = get.byClass("resizeTR", oDrag)[0];
var oBR = get.byClass("resizeBR", oDrag)[0];
var oLB = get.byClass("resizeLB", oDrag)[0]; drag(oDrag, oTitle);
//拉四角
resize(oDrag, oLT, true, true, false, false);
resize(oDrag, oTR, false, true, false, false);
resize(oDrag, oBR, false, false, false, false);
resize(oDrag, oLB, true, false, false, false);
//拉四边
resize(oDrag, oL, true, false, false, true);
resize(oDrag, oT, false, true, true, false);
resize(oDrag, oR, false, false, false, true);
resize(oDrag, oB, false, false, true, false); oDrag.style.left = (document.documentElement.clientWidth - oDrag.offsetWidth) / 2 + "px";
oDrag.style.top = (document.documentElement.clientHeight - oDrag.offsetHeight) / 2 + "px";
}
aaa();
}

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

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

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

function searchClick(data, isAddImageMarker) {
if (!data.name) return;
// 添加pop
removeAll();
var floorLayer = map.getFloor(data.fnum); if (isAddImageMarker) {
floorControl.changeFocusFloor(data.fnum);
}
if (data.name == '房间') {
var dom = '<div class="pop-content"><strong>房间 ' + data.id + '</strong><p>经度:' + data.x.toFixed(3) + '</p><p>纬度:' + data.y.toFixed(3) + '</p></div>';
} else {
var shopDatas = getShopMsg(data.id);//数字number
var dom = '<div class="pop-content"><strong>' + data.name + '</strong><p>人流量:' + shopDatas.msgA + '</p><p>营业额:' + shopDatas.msgB + '</p></div>'
}
//添加信息窗
popMarker = new esmap.ESPopMarker({
mapCoord: {
//设置弹框的x轴
x: data.x,
//设置弹框的y轴
y: data.y,
height: 1, //控制信息窗的高度
//设置弹框位于的楼层
fnum: data.fnum
},
//设置弹框的宽度
width: 200,
//设置弹框的高度
height: 120,
marginTop: 10,
//设置弹框的内容
content: dom,
// content: '<input id="pop-input" type="text"/>',
closeCallBack: function () {
//信息窗点击关闭操作
// alert('信息窗关闭了!');
},
});
$(".es-control-popmarker input").val('✖'); // 手动添加close按钮value
}

效果图如下:

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

//创建360°图片标注到各层
function showImageMarker() {
var _arr = [{
fnum: 1,
node: [{
x: 12683473.823037906,
y: 2557891.805802924,
},
{
x: 12683424.1333389,
y: 2557880.7494297,
}
]
}, {
fnum: 2,
node: [{
x: 12683473.823037906,
y: 2557891.805802924,
},
{
x: 12683424.1333389,
y: 2557880.7494297,
}
]
}, {
fnum: 3,
node: [{
x: 12683473.823037906,
y: 2557891.805802924,
},
{
x: 12683424.1333389,
y: 2557880.7494297,
}
]
}]
for (var el of _arr) {
var floorLayer = map.getFloor(el.fnum);
var im_layer = new esmap.ESLayer('imageMarker');//创建图层
im_layer.name = 'mylayer';//给图层命名
var index = 1;
for (var el2 of el.node) {
var im = new esmap.ESImageMarker({
x: el2.x,
y: el2.y,
url: 'image/360.png',
id: index++,
size: 50,
name: 'myMarker',
zoom: 2,
})
im_layer.addMarker(im);
floorLayer.addLayer(im_layer);
}
}
}

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

function CreatePanorama(prop) {
var camera, scene, renderer, container, mesh;
var texture_placeholder,
target = new THREE.Vector3();//创建3维向量 this.container = container;
this.panorama = prop.panorama;
this.camera = camera;
this.scene = scene;
this.renderer = renderer;
this.mesh = mesh; function init() {
container = prop.container;
camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 1, 1100);
scene = new THREE.Scene();
texture_placeholder = document.createElement('canvas');
texture_placeholder.width = 128;
texture_placeholder.height = 128; var context = texture_placeholder.getContext('2d');
context.fillStyle = 'rgb( 200, 200, 200 )';
context.fillRect(0, 0, texture_placeholder.width, texture_placeholder.height); if (prop.six) {
var materials = [
loadTexture(prop.panorama + 'r.jpg'), // right
loadTexture(prop.panorama + 'l.jpg'), // left
loadTexture(prop.panorama + 'u.jpg'), // top
loadTexture(prop.panorama + 'd.jpg'), // bottom
loadTexture(prop.panorama + 'b.jpg'), // back
loadTexture(prop.panorama + 'f.jpg') // front
];
var matss = new THREE.MultiMaterial(materials)
mesh = new THREE.Mesh(new THREE.BoxGeometry(300, 300, 300, 7, 7, 7), matss);
} else {
var geometry = new THREE.SphereGeometry(100, 64, 64, -1.5707963267948966);
var material = new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load(prop.panorama),
})
mesh = new THREE.Mesh(geometry, material);
}
mesh.scale.x = -1;
scene.add(mesh); renderer = new THREE.WebGLRenderer({//创建一个webGL渲染器,renderer
antialias: true
})
renderer.setPixelRatio(window.devicePixelRatio);//设备设置像素比
renderer.setSize(container.clientWidth, container.clientHeight);//调整输出canvas尺寸 container.appendChild(renderer.domElement);
//监听鼠标各种事件
   container.addEventListener('mousedown', onDocumentMouseDown, false);
container.addEventListener('mousemove', onDocumentMouseMove, false);
container.addEventListener('mouseup', onDocumentMouseUp, false);
container.addEventListener('wheel', onDocumentMouseWheel, false); container.addEventListener('touchstart', onDocumentTouchStart, false);
container.addEventListener('touchmove', onDocumentTouchMove, false); window.addEventListener('resize', onWindowResize, false);
}
}

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

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

// 通过店名搜索地图中店铺  搜索框函数
function searchByName(name) {
Listmodel.item = [];
if (!name) return;
var data = map.mapService.sourceData.floors;//获取地图信息
for (var ele of data) {//遍历获取到的信息
for (var i in ele.Rooms) { //遍历获取到数组里的Rooms
var el = ele.Rooms[i];
if (el.name) {
var a = el.name.indexOf(name);//查找输入的店名是否在地图内存在
if (a != -1) {//如果存在
var obj = {
x: el.CenX,
y: el.CenY,
id: el._id,
fnum: ele.floornum,
name: el.name
}
Listmodel.item.push(obj);//把输入的店名信息存入数组
}
}
}
}
}

效果如下图:

  以上就是我就商场管理者在管理过程中所面临的一些问题,开发的商场管理系统的一个界面,当然我只是简单实现了一些功能,在实际开发过程可根据实际情况定制一些功能方案,从而达到管理者高效管理的目的。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. CentOS6系列系统启动常见故障排查与解决方法

    情景一.内核文件损坏 /boot/vmlinuz-2.6.32-642.el6.x86_64 内核文件 1.故障现象 2.解决方法:挂载光盘,进入rescue(救援)模式 3.选择--English- ...

  2. 单片机开发——02工欲善其事必先利其器(Proteus软件安装破解)

    在单片机开发工程中,博主经常通过模拟软件Proteus进行模拟仿真,将编译生成的"HEX"文件下载在单片机芯片中,然后进行后期的debug工作,当模拟仿真完成之后,进行硬件测试部分 ...

  3. 记一次webpack打包优化

    未进行打包优化的痛点: 随着项目的不断扩大,引入的第三方库会越来越多,我们每次build的时候会对所有的文件进行打包,耗时必定很长,不利于日常开发. 解决思路: 第三方库我们只是引入到项目里来,一般不 ...

  4. linux下svn(subversion)服务端添加工程及配置权限

    linux下svn(subversion)服务端添加工程及配置权限 转载请注明源地址:http://www.cnblogs.com/funnyzpc/p/9010507.html 此篇我只是将所做过的 ...

  5. Flask请求扩展和数据库连接池

    1.1.Flask之请求扩展 #!/usr/bin/env python # -*- coding:utf-8 -*- from flask import Flask, Request, render ...

  6. 洛谷 P1057 解题报告

    P1057 传球游戏 题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹 ...

  7. Python_字符串连接

    #join() 与split()相反,join()方法用来将列表中多个字符串进行连接,并在相邻两个字符串之间插入指定字符 li=['apple','peach','banana','pear'] se ...

  8. [Mysql]——通过例子理解事务的4种隔离级别(转)

    SQL标准定义了4种隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的. 低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销. 一.事务隔离级别分类 第1级别:R ...

  9. PAT1027:Colors In Mars

    1027. Colors in Mars (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue People ...

  10. spring中bean的scope属性理解

    bean的scope属性有prototype,singleton,request, session几个属性 spring和struts2整合的时候,struts2的action要配置成scope=&q ...