最近开发快应用的时候,我现快应用没有自带的相关室内地图构建功能,有的只是调用百度地图的室外地图功能。
(不过这可能是个共有的盲点,其他前端也都得用原生组件搭建室内地图吧)
进入正题,先给大家看搭建的效果:
其中“+”“-”实现缩放,“B”实现复位功能,“-1”处是选择楼层
下面我将一步一步地讲解如何构建室内地图。
1.图片缩放、平移、复位、切换
这些是地图的基本构建元素,我们先从这个问题做起。
快应用可能由于生态还不健全,这一块还找不到直接的学习资料。不过快应用是基于H5,JS的,咱们可以从这些地方找。
这里参考了这篇博客:使用HTML5 canvas做地图(3)图片加载平移放大缩小
但从H5转化到快应用还是有很多工作要做的,这里当做大家有canvas的经验了,直接贴代码了
<template>
<div class="main">
<div class="footer-container">
<input class="button" type="button" value="+" onclick="largeScale"></input>
<input class="button" type="button" value="-" onclick="smallScale"></input>
<input class="button" type="button" value="B" onclick="middleScale"></input>
<select class="button" onchange="pickMap" >
<option for="{{floor}}" selected={{$item.selected}}>{{$item.id}}</option>
</select>
</div>
<div class="content">
<canvas id="indoormap" ontouchstart ='onMouseDown' ontouchmove='onMouseMove'></canvas>
</div>
</div>
</template>
<script>
export default{
data:{
x1:'', //平移用的坐标
x2:'',
y1:'',
y2:'',
context2D:'',
img:'',
imgX:0,
imgY:0,
imgScale:1,
floor:[
{'id':-1,'selected':'true',pic:'../Common/1.jpg'},//粘贴你自己的图片地址
{'id':2,'selected':'false',pic:'../Common/a.jpg'},
{'id':3,'selected':'false',pic:'../Common/3.jpg'}
],
},
pickMap(e){
for(var i=0;i<this.floor.length;i++){
if(this.floor[i].id==e.newValue){
this.img.src=this.floor[i].pic
}
}
this.img.width=1000
this.img.height=1500
this.clearImage()
this.img.onload = () => {
this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
}
},
onShow() {
const canvas = this.$element('indoormap') //获取 canvas 组件
this.context2D = canvas.getContext('2d') //获取 canvas 绘图上下文
this.img = new Image()
for(var i=0;i<this.floor.length;i++){
if(this.floor[i].selected=='true'){
this.img.src=this.floor[i].pic
}
}
this.img.onload = () => {
this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
}
},
onMouseDown(event){
const data=event.touches[0]
this.x1=data.clientX
this.y1=data.clientY
//
},
onMouseMove(event){
//擦除原来的图片
this.clearImage()
const data=event.touches[0]
this.x2=data.clientX
this.y2=data.clientY
this.imgX+=this.x2-this.x1
this.imgY+=this.y2-this.y1
console.log(this.imgY)
//console.log(event.touches[0])
this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
this.x1=this.x2
this.y1=this.y2
},
largeScale(){
this.clearImage()
if(this.imgScale<2.4){this.imgScale+=0.2}
this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
},
smallScale(){
this.clearImage()
if(this.imgScale>0.6)this.imgScale-=0.2
this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
},
middleScale(){
this.clearImage()
this.imgScale=1
this.imgX=0
this.imgY=0
this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
},
drawImage(img,imgX,imgY,imgScale){
this.context2D.drawImage(img,0,0,img.width,img.height,
imgX,imgY,img.width*imgScale,img.height*imgScale)
},
clearImage(){
this.context2D.clearRect(this.imgX,this.imgY,this.img.width*this.imgScale,this.img.height*this.imgScale)
}
}
</script>
<style>
.main{
flex-direction:column;
flex-wrap:wrap;
align-content:flex-start;
}
.content{
width:100%;
height:1500px;
}
.footer-container {
position: fixed;
flex-direction:column;
width:100px;
height: 600px;
top:400px;
left:0;
background-color: transparent;
}
.button{
text-align: center;
font-size: 40px;
font-family: '微软雅黑';
margin-top:40px;
width:100px;
height:100px;
color:white;
background: linear-gradient(rgb(50, 200, 150),rgb(0, 150, 120));
}
</style>
2.地图元素构建
从上面一步,大家应该发现一个问题:图片放大就会失真(模糊)!这是因为我们采用的是位图,而地图不应该因放大而模糊,所以我们应该用矢量图。
普通图片在PS下设置路径转成ai文件到Adobe Illustrator,进行颜色填充,可以转化为矢量图。但这个过程不仅复杂,最关键的是快应用不支持打开这种矢量图。
那么,我们只能自己用canvas画出矢量图了,canvas画出的矩阵等图形都是矢量化的。
这里我又参考了一篇博客:基于HTML5 Canvas绘制的支持手势缩放的室内地图
该文章的思路都是基于H5和JS的,后面关于缩放的内容都是不能照搬的。所以才有了第一步工作。
2.1地图数据构建
其中构建地图数据可以通过以下JSON格式,变量名非常明了,这里就不赘述了。
[
{
"title": "Toilet",
"x": 0,
"y": 0,
"width": 171,
"height": 283,
"color": "rgba(76, 181, 216, 0.2)",
"textcolor": "black",
"bordercolor": "rgba(76, 181, 216, 1)",
"imageurl": "http://192.168.1.106:8000/static/images/Toilet.png"
}
,
{
"title": "UNIQLO",
"x": 0,
"y": 284,
"width": 171,
"height": 286,
"color": "rgba(230, 110, 250, 0.2)",
"textcolor": "black",
"bordercolor": "rgba(230, 110, 250, 1)",
"imageurl": "http://192.168.1.106:8000/static/images/UNIQLO.png"
}
]
这样的数据就可以构造出一层楼的地图。多个这样的文件就是一栋建筑的地图数据。
最终实现
最后贴出完整的实现代码
<template>
<div class="main">
<div class="footer-container">
<input class="button" type="button" value="+" onclick="largeScale"></input>
<input class="button" type="button" value="-" onclick="smallScale"></input>
<input class="button" type="button" value="B" onclick="middleScale"></input>
<select class="button" onchange="pickMap" >
<option for="{{floor}}" selected={{$item.selected}}>{{$item.id}}</option>
</select>
</div>
<div class="content" >
<canvas id="indoormap" ontouchstart ='onMouseDown' ontouchmove='onMouseMove'></canvas>
</div>
</div>
</template>
<script>
import fetch from '@system.fetch'
export default{
data:{
MAPINFO:[],
floor:[
{'owner':'building1','id':-1,'selected':'true','mapUrl':'http://192.168.1.106:8000/static/building1/map1.txt'},//这里mapUrl请替换为你的地图数据访问地址
{'owner':'building1','id':2,'selected':'false','mapUrl':'http://192.168.1.106:8000/static/building1/map2.txt'},
{'owner':'building1','id':3,'selected':'false','mapUrl':'http://192.168.1.106:8000/static/building1/map3.txt'}
],
x1:'', //平移用的坐标
x2:'',
y1:'',
y2:'',
devX:0,
devY:0,
mapUrl:'',//地图信息url
canvas:'',
context2D:'',
imgScale:1,
},
onInit() {
for(var i=0;i<this.floor.length;i++){
if(this.floor[i].selected=='true'){
this.mapUrl=this.floor[i].mapUrl
}
}
fetch.fetch({
url:this.mapUrl,
success: res=>{
this.MAPINFO=JSON.parse(res.data)
this.canvas = this.$element('indoormap') //获取 canvas 组件
this.context2D = this.canvas.getContext('2d') //获取 canvas
this.DrawMapInfo()
}
})
},
pickMap(e){
for(var i=0;i<this.floor.length;i++){
if(this.floor[i].id==e.newValue){
this.mapUrl=this.floor[i].mapUrl
}
}
fetch.fetch({
url:this.mapUrl,
success: res=>{
this.MAPINFO=JSON.parse(res.data)
this.DrawMapInfo()
}
})
this.DrawMapInfo()
},
onMouseDown(event){
const data=event.touches[0]
this.x1=data.clientX
this.y1=data.clientY
},
onMouseMove(event){
//擦除原来的图片
this.clearImage()
const data=event.touches[0]
this.x2=data.clientX
this.y2=data.clientY
this.devX+=this.x2-this.x1
this.devY+=this.y2-this.y1
//console.log(event.touches[0])
this.DrawMapInfo()
this.x1=this.x2
this.y1=this.y2
},
clearImage(){
this.context2D.clearRect(0,0,1500,1500)
},
largeScale(){
if(this.imgScale<1.6){this.imgScale+=0.2}
this.DrawMapInfo()
},
smallScale(){
if(this.imgScale>0.6)this.imgScale-=0.2
this.DrawMapInfo()
},
middleScale(){
this.imgScale=1
this.devX=0
this.devY=0
this.DrawMapInfo()
},
DrawMapInfo:function(){
this.clearImage()
for (var i = 0; i < this.MAPINFO.length; i++) {
this.DrawBlock(this.context2D,this.MAPINFO[i].x*this.imgScale+this.devX,
this.MAPINFO[i].y*this.imgScale+this.devY,
this.MAPINFO[i].width*this.imgScale, this.MAPINFO[i].height*this.imgScale,
this.MAPINFO[i].title, this.MAPINFO[i].color, this.MAPINFO[i].textcolor,
this.MAPINFO[i].bordercolor, this.MAPINFO[i].imageurl)
}
},
DrawBlock:function(context2D,x, y, width, height, text, backgroudcolor, textcolor, bordercolor, imageurl) {
//画框图
context2D.fillStyle = backgroudcolor
context2D.fillRect(x, y, width, height)
//画边界
context2D.strokeStyle = bordercolor
context2D.lineWidth = 0.8
context2D.strokeRect(x, y, width, height)
//画文字和图标,文字小则不画图
context2D.fillStyle = textcolor
const textsize = width * height / 2000
//console.log(textsize)
if (textsize > 15) {
context2D.font = 20 + 'pt Microsoft YaHei'
if (imageurl != "") { //画图标
const image = new Image()
image.src = imageurl
//image.onload = () => {
context2D.drawImage(image, x + width / 7, y + height / 2 - 25 , 30, 30)
//}
}
}
else {
context2D.font = 15 + 'pt Microsoft YaHei'
}
context2D.fillText(text, x + width / 7 + 40, y + height / 2)
},
}
</script>
<style>
.main{
background-color: rgba(220, 241, 220, 0.856);
flex-direction:column;
flex-wrap:wrap;
align-content:flex-start;
}
.indoormap{
width:100px;
height:100px;
}
.content{
width:100%;
height:1500px;
}
.footer-container {
position: fixed;
flex-direction:column;
width:100px;
height: 600px;
top:400px;
left:0;
background-color: transparent;
}
.button{
text-align: center;
font-size: 40px;
font-family: '微软雅黑';
margin-top:40px;
width:100px;
height:100px;
color:white;
background: linear-gradient(rgb(50, 200, 150),rgb(0, 150, 120));
}
</style>
其中floor数据可以存入后台的数据库,事先做一次查询的话,就实现了选择建筑物。
事实上,canvas还有三维的表现形式,理论上我们可以做出三维的动态地图。这个我们以后再说吧。