现在的商场管理者在管理商场的同时面临着一些无法避免的问题比如:人员监管不到位、效率低下、商场同质化严重,人流量少等。发现了这些问题作为开发人员的我们怎能视而不见,我们的责任就是发现问题解决问题,提供更好更智能的服务。因此就此问题我们想出了相应的解决办法,使用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. 利用分支限界法求解单源最短路(Dijkstra)问题

    分支限界法定义:采用Best fist search算法,并使用剪枝函数的算法称为分支界限法. 分支限界法解释:按Best first的原则,有选择的在其child中进行扩展,从而舍弃不含有最优解的分 ...

  2. CentOS 6 安装Hadoop集群

    hadoop2.6.4安装包(链接:https://pan.baidu.com/s/15qHpdoLBQHP4HdxOJzNcsg 密码:5hel) 1.准备Linux环境 1.0先将虚拟机的网络模式 ...

  3. C# DataGridView绑定List对象时,利用BindingList来实现增删查改

    当DataGridView的DataSource是DataTable的时候,DataTable的数据改变时,DataGridView的数据会随之改变,无需重新绑定到DataGridView.  当Da ...

  4. Tomcat配置与优化(内存、并发、管理)与性能监控

    原文链接:http://blog.csdn.net/xyang81/article/details/51530979 一.JVM内存配置优化 在开发当中,当一个项目比较大时,依赖的jar包通常比较多, ...

  5. Java线程池中submit() 和 execute()方法的区别

    两个方法都可以向线程池提交任务, execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorS ...

  6. MySQL 数据库 Query 的优化

    理解MySQL的Query Optimizer MySQL Optimizer是一个专门负责优化SELECT 语句的优化器模块,它主要的功能就是通过计算分析系统中收集的各种统计信息,为客户端请求的Qu ...

  7. Unity文档阅读 第一章 入门

    Before you learn about dependency injection and Unity, you need to understand why you should use the ...

  8. Oracle-11:联合查询

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 联合查询的实际上的意义就是从俩个结果集中拿有特定联系的结果封装为一个结果集 数据库脚本给放一份,供测试使用 c ...

  9. Java历程-初学篇 Day01初识java

    HelloWorld!!!!! 一,第一个java程序的构成 1,外层框架 class 后面的类名必须与文件名相同 起名方法:1)构成只能有_ $ 字母 数字 2)数字不能开头 3)首字母必须大写 4 ...

  10. C语言实现计算双基回文数详解

    双基回文数的定义: 如果一个正整数n至少在两个不同的进位制(二进制<=进制=<十进制)b1和b2下都是回文数,则称n是双基回文数. 根据定义,简单的说就是在二进制到十进制之间(包括十进制和 ...