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

    htmlparser 学习系列 htmlparser 使用法使用与详解

  2. SSM博客

    最近在写基于SSM 的博客,用的是spring+springmvc+mybatis搭建的,用的maven+github管理项目,目前完成了登录注册以及用户管理员的增删改查,后续的功能还需要完善,如何建 ...

  3. mac 登录亚马逊云服务器报错:Permission denied (publickey).

    申请的亚马逊云服务器EC2,实例为ubuntu系统 一.打开终端,定位到放置密钥的文件夹: 二.确保私有秘钥不是公开可见的: p.p1 { margin: 0.0px 0.0px 0.0px 0.0p ...

  4. Oracle-04:DDL语言数据表的操作

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- DDL操作: --创建学生表 create table student( sno number(4) not  ...

  5. 解决C语言程序报错:return type defaults to‘int’

    下面是通过自定义一个函数printN,之后在main函数中调用printN,使得可以通过输入整数N,将从1到N的全部整数都打印出来的程序. 但是在编译过程中却报错: return type defau ...

  6. JVM 线上故障排查基本操作

    # 前言 对于后端程序员,特别是 Java 程序员来讲,排查线上问题是不可避免的.各种 CPU 飚高,内存溢出,频繁 GC 等等,这些都是令人头疼的问题.楼主同样也遇到过这些问题,那么,遇到这些问题该 ...

  7. MIT KIT OpenID Connect Demo Client

    Hello world! You are NOT currently logged in. This example application is configured with several pa ...

  8. jQuery学习之旅 Item3 属性操作与样式操作

    本节将Dom元素的操作:属性操作.样式操作.设置和获取HTML,文本和值.Css-Dom操作. 1.属性操作 <input type="text" name="us ...

  9. Java 字符流文件读写

    上篇文章,我们介绍了 Java 的文件字节流框架中的相关内容,而我们本篇文章将着重于文件字符流的相关内容. 首先需要明确一点的是,字节流处理文件的时候是基于字节的,而字符流处理文件则是基于一个个字符为 ...

  10. tomcat设置端口号,访问指定ip就访问指定项目

    1.修改背景: A.通常我们访问我们的web应用格式为: http://ip:端口号/项目名称 例如: http://127.0.0.1:8080/projectName B.如果想直接输入" ...