技术方案及解决办法
总体方案
此次的百度地图开发项目,因为总体是个比较简单的成果展示类,同时百度地图api 和Echarts 等可直接使用的现成示例是使用html 的JavaScript 直接运行的,所以此次是直接使用HTML+CSS+Javascript 进行前端开发的,同时由于已有的算法是使用python 程序撰写的,所以就使用python中的flask 进行后端开发,便于前端直接调用python算法。
同时呢,因为想要使用element 的样式,这样能够统一风格同时少写点css样式 ,也能更美观,但是又不知道怎么在vue 项目中直接用这些javacript的项目进行嵌入,为确保短时间能够顺利开发,所以直接使用了html引入vue和element的js进行使用,这样的引入可以造成奇妙的现象,有着vue的写法,同时也可以使用html本身的js写法,emmmm,很奇妙。
基础框架
前端网页
其实img 文件夹可以放到css 文件夹中,这样的考量是如果不使用web服务器也能直接读取到图片,因为默认的加载路径会到css/img 下进行。
css文件夹下放置所有的css文件,img放置了所有的图片,js放置了所有javascript文件(自定义或者本地引入),index.html则是所有的网页内容,没错一个网页。
后端程序
data中装在所有数据(处理前后)、env则是使用了虚拟环境用来控制环境、flask.py则是后端的框架文件,然后就可以通过这个接口调用其他的函数算法了。
结合搭建方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var msg = {
canshu1:'哈哈哈哈',
canshu2:'tgmmmmmm',
flag2:'这里的东西全凭自定义,使用对象方式构建'
};
var senddata = JSON.stringify(msg);
$.ajax({
url: "http://127.0.0.1:5000/test",
type: "POST",
data: senddata,
dataType: "json",
success: function (data) {
console.log(data['参数名'])
}
});
</script>
</body>
</html>
from flask import Flask,request,jsonify
import json
@app.route('/')
def hello_world():
return 'Hello World'
@app.route('/test', methods=['GET', 'POST'])
def calc1():
recv_data = request.get_data()
if recv_data:
json_re = json.loads(recv_data)
ret = json_re['参数名']
return jsonify({"自己定义的返回参数名": ret})
else:
print("receive data is empty")
if __name__ == '__main__':
app.run()
跨域解决
这样子前后端还不能交互,因为后端运行在单独的端口上,在其他端口或其他ip的ajax调用会出现跨域问题,所以需要配置一下允许跨域访问。这里我们使用最简单粗暴的方法。
from flask import Flask,request,jsonify
import json
from flask_cors import CORS
app = Flask(__name__)
CORS(app, supports_credentials=True)
@app.route('/')
def hello_world():
return 'Hello World'
if __name__ == '__main__':
app.run()
python处理excel
问题描述
在使用echarts时,他的数据量比较大,同时有特定格式,但是我们的数据是存储在excel中的,所以需要将excel中的数据处理成我们需要的格式,然后复制到网页的echarts数据需求处即可。
解决办法
import xlrd
import xlwt
def read_excel():
workBook = xlrd.open_workbook('data/shuju.xls');
allSheetNames = workBook.sheet_names();
print(allSheetNames);
sheet1Name = workBook.sheet_names()[0];
print(sheet1Name);
sheet1_content1 = workBook.sheet_by_index(0);
sheet1_content2 = workBook.sheet_by_name('Sheet1');
print(sheet1_content1.name,sheet1_content1.nrows,sheet1_content1.ncols);
rows = sheet1_content1.row_values(3);
cols = sheet1_content1.col_values(2);
print(rows);
print(sheet1_content1.cell(1, 0).value);
print(sheet1_content1.cell_value(2, 2));
print(sheet1_content1.row(2)[2].value);
print(int(sheet1_content1.cell(0,0).value))
print(sheet1_content1.cell(1, 0).ctype);
f = open('data/data.txt', 'a')
print('[', file=f,end="")
for i in range(sheet1_content1.nrows):
for j in range(sheet1_content1.ncols):
print("["+str(i)+","+str(j)+","+str(int(sheet1_content1.cell(i,j).value))+"],",file=f,end="")
print("]")
f.close()
if __name__ == '__main__':
read_excel();
网页结构
vue实例
网页结构得构造比较简单,因为总共就几个页面,就偷懒直接没有多个网页,我直接用了vue框架中的v-show 来控制dom的显示与隐藏,变成了类似网页切换的效果。
可以看到,其中body中就一个id为vueapp的div,然后内部包括了header头部和main主体部分。
然后再main中依次撰写了page1到page4四个页面,使用v-show 切换控制显示。
然后再body结束后,html结束前构建了vue的实例,el 指定的dom元素,data 是其中可以双向绑定的数据,computed 是计算属性,如果其依赖的值变化,他就会重新计算更新自己的值,mounted 则是在加载vue实例时就会执行的内容,methods 则是vue中撰写函数的地方。
内容引入
所有的样式引入和百度api的引入放在了标签中,如图所示,有自己的css,也有通用的
然后所有的js文件则作为script放在了body结束,html前进行了引入
element布局空元素
在element中的布局中包含了行列分别,el-row,el-col,一行将被分割成24列用来布局,但是我利用他来做部分内容居中,部分内容在最右侧,所以我就想这样
结果,令人生气的是,如果我没给div内容,他就是个隐藏的东西,不占我分配的4列,就变成了
!好生气,然后我就随便填了点东西,再把他的颜色设为了背景色,融为一体哈哈哈哈
<el-row>
<el-col :span="4">
<div class="kb">-</div>
</el-col>
<el-col :span="16">
<div>诶居中的内容</div>
</el-col>
<el-col :span="4">
<div>诶靠右侧的</div>
</el-col>
</el-row>
.kb{
background-color: rgb(222, 222, 222);
}
tips:我写总结的时候突然意识到,我是不是给他一个高度和宽度就可以了,就算是空的也行!
v-for内绑定数据
问题描述
现在有一个列表,从中选择几项,然后根据选择的这几项对其进行评分,难点在于,其是两个步骤分离的,不选择的不会进行显示和评分
解决方案
首先把第一个选择的放入列表,作为下标,说明那些项是被选择了的,然后对其进行循环,同时在循环内部用一个新的列表进行绑定,这样内部的列表就可以用下标进行绑定,比如选择0,1,3项,那么到时候的内部绑定的列表就会使x,x,x,第三项的地方会空出来。
<div v-for="x in selectList">
<el-tag>{{allList[x].label}}</el-tag>
<el-select v-model="bindList[x]">
<el-option v-for="item in otherList" :key="item.value" :label="item.label" :value="item.value">
<span>{{ item.label }}</span>
<span>{{ item.value }}</span>
</el-option>
</el-select>
</div>
以上代码实现了,从所有指标中选择了部分指标,然后对其使用select组件进行选择值,值会绑定到其对应下标的bindList中。
:placeholder动态绑定
问题描述
希望在一个循环中对:placeholder内容进行动态显示,不同的框中不同,并且根据特定的参数计算出所需要的内容
解决办法
利用计算属性,将其参数传递到其中,由于参数变化,引起了其值的变化,成功动态绑定
同时由于计算属性本身没有参数,所以使用内传参 的方式进行。
<el-input v-for="x in list" :placeholder="myplaceholder(x)">
computed: {
myplaceholder() {
return function (x) {
return x+2;
}
},
}
el-table的动态宽度问题
问题描述
该问题的引起是因为在其他地方都可以使用vh 或者vw 代替,这样子可以把网页做成动态的缩放效果,但是不知道为什么el-table 的width中不支持这个属性,搞了半天也没有解决。
解决办法
最终没有解决,由于只在本地展示,所以使用了本地的绝对像素,但是感觉不好使,在其他地方就会乱掉!难受!!有办法解决的话T我。
文件上传自定义框
问题描述
一般来说文件上传可以直接使用file类型的input,但是其样式不是我们喜欢的,那么怎么可以比如点击一个按钮实现文件上传按钮呢?
解决办法
这个可以首先将input隐藏起来,然后利用button绑定事件到input的点击事件,这样就间接调用文件上传。
<input id="upload" type="file" style="display:none" onchange="getfile()">
<el-button @click="uploadfile">上传文件</el-button>
function uploadfile() {
document.getElementById('upload').click();
}
echarts 正交投影地球使用并diy旋转,本地化数据
问题描述
现在希望对正交地球投影的图标进行使用,并且让他自动进行旋转。同时因为其每次加载都需要从网络进行异步加载数据会造成打开网页的卡顿和显示不美观,希望能够从本地进行加载。
解决办法
1. 正交地球投影使用
基本复制黏贴就可以用,此处对代码进行简化,只留下关键部分
myChart.showLoading();
let projection;
$.when(
$.get(ROOT_PATH + '/data/asset/geo/world.json'),
$.getScript('https://cdn.jsdelivr.net/npm/d3-array'),
$.getScript('https://cdn.jsdelivr.net/npm/d3-geo')
).done(function (res) {
myChart.hideLoading();
const graticuleLineStrings = [];
for (let lng = -180; lng <= 180; lng += 10) {
graticuleLineStrings.push(createLineString([lng, -80], [lng, 80]));
}
res[0].features.unshift({
geometry: {
type: 'MultiLineString',
coordinates: graticuleLineStrings
},
properties: {
name: 'graticule'
}
});
echarts.registerMap('world', res[0]);
projection = d3.geoOrthographic();
option = {
geo: {
map: 'world',
projection: {
project: (pt) => projection(pt),
unproject: (pt) => projection.invert(pt),
stream: projection.stream
},
}
};
myChart.setOption(option);
});
app.config = {
rotateX: 0,
rotateY: 0,
onChange() {
projection && projection.rotate([app.config.rotateX, app.config.rotateY]);
myChart.setOption({
geo: {}
});
}
};
function createLineString(start, end) {
const points = [];
return points;
}
2.本地化数据
分析了一下,他的网络加载数据主要加载了三个world.json 和d3-array 和d3-geo
百度了一下,world.json是世界地图的json格式,然后d3两个是处于一个叫d3 的可视化库的,用在这里也正常,所以我们需要做的就简单了,把这两个数据下载下来,然后本地加载,d3还好说,导入成js就行。但是json格式就比较麻烦,因为需要至于web应用服务器中才好加载,此时不好进行加载了。
但是!聪明绝顶的我,想到了一个办法,我们看到echarts.registerMap('world', res[0]); 这么一行代码,echarts是全局的指代,那么我们是否可以从其他地方进行地图的注册,然后再导入过来呢,嘻嘻。
百度搜了下,换了个逻辑搜索world.js 欧克,成了,有人做成了js文件,直接就注册好了,好人一生平安。其实自己也能写,让代码自动生成正确格式就行,但是当时时间有限,也没有了解过world.json的格式。这里放个[world.js链接](world.js · master · mirrors / fuhang-lm / echarts · GitCode),里面包含了汉化版本,很不错。
但是同时有个问题,我们发现直接使用的话,没有经纬度,因为我们明显看到了,代码中有添加经纬度这一步骤,所以直接进入到world.js中进行编辑,添加进去即可。
嗯有哦人帮忙做好了大部分的感觉真好,开源真好。
3.diy旋转
其实到这里基本结束了,也就是想办法再浏览器中循环调用setoption就行了。本来想使用setInterval来做定时器,但是其必须设定间隔事件,而屏幕刷新率支持的基本上就是17ms最佳了,再低也没有效果了。
但是百度了一下,都说使用自带的requestAnimationFrame 要好得多,性能提升不说,流畅性也有保障,可以不必设定时间间隔,其自动进行。
drawearth() {
window.cancelAnimationFrame(this.drawearth)
this.rotateX += 2
if (this.rotateX > 360)
this.rotateX = 2
projection && projection.rotate([this.rotateX - 180, this.rotateY]);
option && myChart.setOption({
geo: {}
});
this.rotateTimer = window.requestAnimationFrame(this.drawearth)
},
设定以上函数,然后在创建vue实例的时候调用一下就可以了
this.rotateTimer = window.requestAnimationFrame(this.drawearth)
前面的值是对这个动画帧的一个标注,如果要停止的话,需要知道这个id
window.cancelAnimationFrame(this.rotateTimer)
鼠标移入echarts绑定事件
问题描述
其实还是刚刚哪个地图,只是我门还需要一个鼠标移动到某个区域就进行展示名字,同时停止旋转否则太快看不清
解决方案
这里有两个方案,第一个就是绑定的鼠标移入事件,这里我们的停止时使用的这个绑定,然后停止掉指定id的动画帧,这样就停止旋转了,至于那个展示名字,我是针对的其配置项tooltip进行提示,对其展示数据的进行了格式化的展示
tooltip: {
formatter: function (params) {
var value = "值:" + params.value,
name = "名字:" + params.name;
return name + "<br/>" + value;
},
borderWidth: 5,
borderColor: '#8B2E41'
}
// 添加鼠标移入暂停动画
myChart.on('mouseover', function (params) {
window.cancelAnimationFrame(vue.rotateTimer);
});
// 添加鼠标单击开始动画
myChart.on('click', function (params) {
vue.rotateTimer = window.requestAnimationFrame(vue.drawearth);
});
ajax中的this丢失
问题描述
在ajax的回调函数中,是不认同前文的this指针的,也就是说,他如果在vue实例中,是无法调用到其中的data的数据的,这样就很难受,无法根据返回修改并显示
解决方法
-
提前把this指针存起来,用另外一个来代替,然后可以传入进行使用 -
直接把Vue实例赋值对象,然后使用他来调用,亲测可用
存起来后面用。
js数据处理,保留小数位数,取整
如题
tips: 取整数,向上取整:Math.ceil(3 / 2)
保留小数位数: (7/2).toFixed(3)
重新加载echarts图表
问题描述
在前期的时候,希望不刷新网页但是图表可以重新动态展示,增加高级感,但是我本身是一个网页,其实早就加载完毕了,所以需要重新设定一下进行加载
### 解决办法
这里本来重新尝试setoption进行 ,但是可以可惜他没有,所以尝试了移除元素重新加载
document.getElementById('zhandian').removeAttribute('_echarts_instance_');
let zhandianChart = echarts.init(document.getElementById('zhandian'));
然后对setOption也更改了
zhandianChart.setOption(zhandianoption, true);
查了一下,这里第二个参数是用与设定是否合并数据,true就是not merge
百度地图自适应,最佳视角
问题描述
因为使用了百度地图,在上面进行了部分的点线标注等,但是其展示的视角可能不能够良好的展示,但是如果固定的话,会导致不同的结果展示又不适配了
解决方案
想了半天结果百度地图api已经想好了,给了我们一个setViewport的函数,只要使用,我们就能对给定参数的点或线进行最佳视角调整,还可以动态变化或者说在此基础上缩放自定义
所以我就直接绑定了一个地图加载完毕事件,然后让他加载完毕就调整到限定的结果最佳视角的缩放一级
map.addEventListener('tilesloaded', jiazaiok);
function jiazaiok(e) {
map.setViewport(luxian, {zoomFactor: +1})
}
地图上图标动起来,路线轨迹
问题描述
这个主要是希望能够添加一辆公交车,在我们的路线上进行行驶
解决方案
百度的拓展GL中包含有路书这么一个东西,它能够制作动态的图标在上面运动,但是他的封装只进行一次行走。
lushu1 = new BMapGLLib.LuShu(map, luxian, {
icon: new BMapGL.Icon("img/公交车.png", new BMapGL.Size(30, 30), {
anchor: new BMapGL.Size(10, 20)
}),
speed: 4000,
enableRotation: true
});
lushu1.start();
这样就可以动起来了,同样需要先导入整个GL或者说lushu.js
因为我需要其进行循环走动,所以我对lushu.js自定义了一点,我判断他到达终点又将其放置于起点,让他继续开。
3d 视角路线追随
问题描述
这个功能是先看到百度的示例,非常高级,3d视角的在城市穿梭,但是他只是一个简单的实例,需要用的5个参数,分别是center 、zoom 、tilt 、heading 、percentage 代表了中心点、缩放等级、倾斜角度、宣传角度、在总路线中的进度,他的基本逻辑就是一系列点确定了位置,然后通过其在总长度的百分比,控制补帧,进行运动。所以有一个问题就是我们有了路线以后得构造除了点和缩放等级以外的参数。
解决办法
我们可以对每个点均匀的给定进度,其余都保持不变,但是由于点之间距离不同,会变成一会快一会满,所以希望能够通过已经走过的距离占总长度的距离这样来进行构造,让其能够全程匀速。
百度地图api提供了一个api,是用来计算两个点之间的距离的,单位是m。getDistance(p1,p2),
有了这个我们就能首先把总长度计算出来,然后逐步构造关键帧,每构建一个就将这段距离加入已走过距离,这样就能持续利用这个占比进行运动,同时可以利用 已经走过/总长 * 总角度 的方法,对角度等进行构建。
var zongchangdu = 0;
for (var i = 0; i < luxian.length - 1; i++) {
var temp = map.getDistance(luxian[i], luxian[i + 1])
if (isNaN(temp))
temp = 0;
zongchangdu += temp
}
var jinxingchangdu = 0;
for (var i = 0; i < luxian.length - 1; i++) {
keyFrames.push({
center: new BMapGL.Point(luxian[i].lng, luxian[i].lat),
zoom: 19,
tilt: 90 * ((jinxingchangdu - zongchangdu / 2) / zongchangdu),
heading: 360 * (jinxingchangdu / zongchangdu),
percentage: jinxingchangdu / zongchangdu,
})
var temp = map.getDistance(luxian[i], luxian[i + 1])
if (isNaN(temp))
temp = 0;
jinxingchangdu += temp
}
var opts = {
duration: 50000,
delay: 3000,
interation: 'INFINITE'
};
animation = new BMapGL.ViewAnimation(keyFrames, opts)
map.startViewAnimation(animation);
自定义倒计时
问题描述
希望在地图上自定义一个空间,进行不断倒计时5、4、3、2、1这样子
解决方案
1. 定时倒计时
setInterval(function () {
div.innerHTML = jishi + " min";
jishi--;
if (jishi < 0)
jishi = 5;
}, 1000);
2. 自定义控件
function TimeControl() {
this.defaultAnchor = BMAP_ANCHOR_TOP_LEFT;
this.defaultOffset = new BMapGL.Size(20, 20)
}
TimeControl.prototype = new BMapGL.Control();
var jishi = 5;
var div = document.createElement('div');
setInterval(function () {
div.innerHTML = jishi + " min";
jishi--;
if (jishi < 0)
jishi = 5;
}, 1000);
TimeControl.prototype.initialize = function (map) {
div.style.cursor = "pointer";
div.style.padding = "7px 10px";
div.style.boxShadow = "0 2px 6px 0 rgba(27, 142, 236, 0.5)";
div.style.borderRadius = "5px";
div.style.backgroundColor = "white";
map.getContainer().appendChild(div);
return div;
}
var jishiControl = new TimeControl();
map.addControl(jishiControl);
高级地球定位
问题描述
希望能够实现从整个地球定位到地面那种很高级的样子
解决办法
很简单,首先设定地图为地球模式,然后设定一个范围待会定位到这里,最后使用一下setViewport即可
map.setMapType(BMAP_EARTH_MAP); // 设置地图类型为地球模式
总结
总的来说,这次还是学到了许多,开发的地图也算还看的过去,主要是查阅文档的能力得到了提升,同时结合文档和自己的需求能够更进一步的进行开发, 这是我最喜欢的快乐,同时体会到了开源代码和开放的快乐,希望自己能早日成为给别人开源现成方法的人。
同时能够体会到开发过程中很多问题,首先对于方案来说,只是考虑了是否技术可行,就是说是否能够逻辑上完成工作,但是并未考虑到有些地方的效率问题,有可能方案可行,但是实际上使用会很卡顿,这样的方案应该找到优化方案才可以,同时以上这些问题的经验也希望能够帮助到其他人吧,帮助不了我自己下次遇到也可以翻一翻,嘻嘻。
|