世界杯夺冠国家

离线地图实现方案+动态轨迹绘制

1.项目简介及技术栈

简介:基于内网离线环境开发

1.考虑到地图问题做了离线地图,使用百度离线API,基于以上底图需要实现点击企业名称后定位到该企业所在位置处。

2.定位过去后通过点击点标记,可展示企业基本信息,通过点击查验按钮,调用腾讯会议API对该企业进行查验呼叫入会

3.企业通过智能眼镜或者单兵设备向web端发送坐标数据,根据数据id跟点坐标去匹配当前企业,在企业周围实现动态轨迹的绘制,实现对查验企业的监管

4.技术栈:SpringBoot+Thymeleaf+Vue+Websocket

2.离线地图下载及部署

2.1 离线地图下载器

网上有很多下载工具,我用到的是一个开源的项目,网盘链接放下面

链接:https://pan.baidu.com/s/1vZwBe2AxoHuVMEqipcN1yA

提取码:47iv

程序账号密码:admin/admin

说明:application.properties文件中的appk需要自己去百度地图申请

2.2 离线地图案例

ps:只需要下载下来瓦片后把瓦片放到tiles文件夹下即可,网盘链接也放上

链接:https://pan.baidu.com/s/1Nli1r8k-1bFZQtpe46peww

提取码:8dxt

文件结构

3.企业点标记及+调用腾讯会议快速入会

3.1 企业点位信息标记

数据目前为写死的假数据前端代码如下

数据

data() {

return {

// 线条样式

polylinePoints:[],

socket:null,

// src:"../static/img/map.jpg",

map: null, // 将地图对象定义为数据属性

form: {

name: '',

region: '',

date1: '',

date2: '',

delivery: false,

type: [],

resource: '',

desc: ''

},

formLabelWidth: '120px',

dialogFormVisible:false,

// 企业基本信息

items:[

{id:"51c89d4f-6623-9d27-43f4-516a0fdc312c",companyName:"危化品公司1",info:"世界500强",address:"山东省济南市历下区",lng: 116.404, lat: 39.915},

{id:"51c89d4f-6623-9d27-43f4-516a0fdc312c",companyName:"危化品公司2",info:"世界500强",address:"山东省济南市历下区",lng: 116.484, lat: 39.955},

{id:"51c89d4f-6623-9d27-43f4-516a0fdc312c",companyName:"危化品公司3",info:"世界500强",address:"山东省济南市历下区",lng: 116.354, lat: 39.815},

{id:"51c89d4f-6623-9d27-43f4-516a0fdc312c",companyName:"危化品公司4",info:"世界500强",address:"山东省济南市历下区",lng: 116.304, lat: 39.995},

{id:"51c89d4f-6623-9d27-43f4-516a0fdc312a",companyName:"危化品公司5",info:"世界500强",address:"山东省济南市历下区",lng: 116.204, lat: 39.985},

{id:"51c89d4f-6623-9d27-43f4-516a0fdc312d",companyName:"济南危化品公司6",info:"世界500强",address:"山东省济南市历下区",lng: 117.010213, lat: 36.608779},

],

}

}

渲染

mounted(){

var map = new BMap.Map("container"); // 创建地图实例

this.map=map;

var point0 = new BMap.Point(117.010213, 36.608768); // 创建点坐标

map.centerAndZoom(point0, 18); // 初始化地图,设置中心点坐标和地图级别

map.enableScrollWheelZoom(true); //开启鼠标滚轮缩放

// 创建点坐标数组

var points = this.items;

// 遍历点坐标数组,创建标记点和文本标注

for (var i = 0; i < points.length; i++) {

var point = new BMap.Point(points[i].lng, points[i].lat);

// 创建点标记

var marker = new BMap.Marker(point);

map.addOverlay(marker);

// 创建信息窗口

var opts = {

width: 200,

height: 100,

title: points[i].companyName,

enableMessage: true // 启用信息窗口发送消息功能

};

var contentHTML = '简介:' + points[i].info;

// 添加按钮元素

contentHTML += '

'; // 右对齐

contentHTML += '';

contentHTML += '

'; // 关闭右对齐

var infoWindow = new BMap.InfoWindow(contentHTML, opts);

// 点标记添加点击事件

marker.addEventListener('click', (function (infoWin, pt, pointInfo) {

return function () {

map.openInfoWindow(infoWin, pt); // 开启信息窗口

// 添加按钮点击事件处理程序

var button = document.getElementById('customButton');

button.addEventListener('click', function () {

// 在这里执行您的事件处理逻辑,pointInfo 包含点位的信息

var obj = JSON.stringify(pointInfo);

axios.get('/hg/createMeeting/'+ pointInfo.id+"?companyName="+pointInfo.companyName)

.then(response => {

// 成功响应后设置 items 的值为获取的数据

console.log(response.data)

var url = response.data.msg;

var link = document.createElement('a');

link.href=url

link.click();

link.remove();

// alert('按钮被点击了!点位信息:' + response.data.msg);

// this.items = response.data;

})

.catch(error => {

console.error('Axios 请求出错:', error);

});

});

};

})(infoWindow, point, points[i]));

// 创建文本标注对象

var labelOpts = {

position: point,

offset: new BMap.Size(-80, -90)

};

var label = new BMap.Label(points[i].companyName, labelOpts);

// 自定义文本标注样式

label.setStyle({

color: 'black',

borderRadius: '5px',

borderColor: '#ccc',

padding: '10px',

fontSize: '16px',

height: '50px',

lineHeight: '30px',

fontFamily: '微软雅黑',

maxWidth:"none"

});

map.addOverlay(label);

}

}

效果图

3.2 点击企业后定位

点击后定位到该企业的点位上,重新设置中心点

点击红色标记呼叫腾讯会议

// 点标记添加点击事件

marker.addEventListener('click', (function (infoWin, pt, pointInfo) {

return function () {

map.openInfoWindow(infoWin, pt); // 开启信息窗口

// 添加按钮点击事件处理程序

var button = document.getElementById('customButton');

button.addEventListener('click', function () {

// 在这里执行您的事件处理逻辑,pointInfo 包含点位的信息

var obj = JSON.stringify(pointInfo);

axios.get('/hg/createMeeting/'+ pointInfo.id+"?companyName="+pointInfo.companyName)

.then(response => {

// 成功响应后设置 items 的值为获取的数据

console.log(response.data)

var url = response.data.msg;

var link = document.createElement('a');

link.href=url

link.click();

link.remove();

// alert('按钮被点击了!点位信息:' + response.data.msg);

// this.items = response.data;

})

.catch(error => {

console.error('Axios 请求出错:', error);

});

});

};

})(infoWindow, point, points[i]));

3.3 呼叫腾讯会议

腾讯会议用的是企业版,需要自己创建应用,获取相关key,具体方式请自行百度。

后台代码

package com.ruoyi.project.hg.util;

import com.alibaba.fastjson.JSONArray;

import com.alibaba.fastjson.JSONObject;

import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.ruoyi.project.hg.tencent.domain.MeetingData;

import com.ruoyi.project.hg.tencent.domain.Settings;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import javax.crypto.Mac;

import javax.crypto.spec.SecretKeySpec;

import java.io.BufferedReader;

import java.io.DataOutputStream;

import java.io.IOException;

import java.io.InputStreamReader;

import java.net.HttpURLConnection;

import java.net.URL;

import java.nio.charset.StandardCharsets;

import java.security.InvalidKeyException;

import java.security.NoSuchAlgorithmException;

import java.util.*;

public class TencentUtil {

private static final Log log = LogFactory.getLog(TencentUtil.class);

static String HMAC_ALGORITHM = "HmacSHA256";

public final static String appId="";

public final static String secretId="";

public final static String sdkId="";

public final static String secretKey="";

public final static String address="https://api.meeting.qq.com";

static char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

public static String bytesToHex(byte[] bytes) {

char[] buf = new char[bytes.length * 2];

int index = 0;

for (byte b : bytes) {

buf[index++] = HEX_CHAR[b >>> 4 & 0xf];

buf[index++] = HEX_CHAR[b & 0xf];

}

return new String(buf);

}

/**

* 生成签名,开发版本oracle jdk 1.8.0_221

*

* @param secretId 邮件下发的secret_id

* @param secretKey 邮件下发的secret_key

* @param httpMethod http请求方法 GET/POST/PUT等

* @param headerNonce X-TC-Nonce请求头,随机数

* @param headerTimestamp X-TC-Timestamp请求头,当前时间的秒级时间戳

* @param requestUri 请求uri,eg:/v1/meetings

* @param requestBody 请求体,没有的设为空串

* @return 签名,需要设置在请求头X-TC-Signature中

* @throws NoSuchAlgorithmException e

* @throws InvalidKeyException e

*/

static String sign(String secretId, String secretKey, String httpMethod, String headerNonce, String headerTimestamp, String requestUri, String requestBody) throws NoSuchAlgorithmException, InvalidKeyException {

String tobeSig =

httpMethod + "\nX-TC-Key=" + secretId + "&X-TC-Nonce=" + headerNonce + "&X-TC-Timestamp=" + headerTimestamp + "\n" + requestUri + "\n" + requestBody;

Mac mac = Mac.getInstance(HMAC_ALGORITHM);

SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), mac.getAlgorithm());

mac.init(secretKeySpec);

byte[] hash = mac.doFinal(tobeSig.getBytes(StandardCharsets.UTF_8));

String hexHash = bytesToHex(hash);

return new String(Base64.getEncoder().encode(hexHash.getBytes(StandardCharsets.UTF_8)));

}

/**

* 获取公共请求头

*

* @param httpMethod 请求方式:POST|GET

* @param requestUri 请求uri

* @param requestBody 请求体 GET方法请求体需传""

* @return 拼接好的请求头

*/

public static Map getHeader(String httpMethod, String requestUri,

String requestBody) {

HashMap header = new HashMap<>(8);

// 请求随机数

String headerNonce = String.valueOf(new Random().nextInt(999999));

// 当前时间的UNIX时间戳

String headerTimestamp = String.valueOf(System.currentTimeMillis() / 1000);

String signature = null;

try {

signature = sign(secretId, secretKey, httpMethod, headerNonce, headerTimestamp, requestUri,

requestBody);

} catch (Exception e) {

log.error("签名生成异常", e);

}

header.put("Content-Type", "application/json");

header.put("X-TC-Key", secretId);

header.put("X-TC-Timestamp", headerTimestamp);

header.put("X-TC-Nonce", headerNonce);

header.put("AppId", appId);

header.put("X-TC-Version", "1.0");

header.put("X-TC-Signature", signature);

header.put("SdkId", sdkId);

header.put("X-TC-Registered", "1");

return header;

}

/**

* 腾讯会议发送post请求 携带生产签名和公共请求头参数

*

* @param address 请求地址

* @param uri 请求uri生产签名使用

* @param requestBody 请求参数

* @return 请求响应结果

*/

public static String sendRequest(String method,String address, String uri, String requestBody) {

if (method.equals("GET"))requestBody="";

//生成公共请求头参数和签名

Map headerMap = getHeader(method, uri, requestBody);

String jsonStr = "";

StringBuilder sub = new StringBuilder();

try {

URL url = new URL(address+uri);

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

// 设置连接请求属性post

conn.setDoOutput(true);

conn.setDoInput(true);

conn.setRequestMethod(method);

// 设置请求头

for (Map.Entry header : headerMap.entrySet()) {

conn.setRequestProperty(header.getKey(), header.getValue());

}

DataOutputStream out =new DataOutputStream(conn.getOutputStream());

out.write(requestBody.getBytes());

out.flush();

out.close();

// 定义BufferedReader输入流来读取URL的响应

int code = conn.getResponseCode();

System.out.print("====="+conn.getResponseMessage());

if (HttpURLConnection.HTTP_OK == code) {

BufferedReader in = new BufferedReader(new InputStreamReader(

conn.getInputStream(), "UTF-8"));

String line;

while ((line = in.readLine()) != null) {

sub.append(line);

}

in.close();

}

return sub.toString();

// log.info("腾讯会议httpPost请求相应信息{}", jsonStr);

} catch (IOException e) {

log.info("腾讯会议http"+method+"发送异常", e);

}

return jsonStr;

}

/**

* 创建会议

*/

public static String creatMeeting(String userId,String companyName) throws JsonProcessingException {

String[] invitees = userId.split(",");

// String string = Arrays.toString(invitees);

// 开始时间

long startTime = System.currentTimeMillis();

// 获取当前时间

Calendar calendar = Calendar.getInstance();

Calendar calendar2 = Calendar.getInstance();

// 增加30分钟

calendar.add(Calendar.MINUTE, 30);

calendar2.add(Calendar.MINUTE, 3);

// 获取增加后的时间戳

long endTime = calendar.getTimeInMillis();

startTime = calendar2.getTimeInMillis();

long endTimeInSeconds = endTime / 1000;

long startTimeInSeconds = startTime / 1000;

String st = String.valueOf(startTimeInSeconds);

String et = String.valueOf(endTimeInSeconds);

String data="{\n" +

"\t\"userid\": \"9d2fbf1c-4376-838a-4d06-ada53d20915b\",\n" +

"\t\"instanceid\": 1,\n" +

"\t\"subject\": \"测试demo\",\n" +

"\t\"type\": 0,\n" +

"\t\"invitees\": [\"51c89d4f-6623-9d27-43f4-516a0fdc312c\"],\n" +

"\t\"start_time\": \"1696823400\",\n" +

"\t\"end_time\": \"1696824000\",\n" +

"\t\"settings\": {\n" +

"\t\t\"mute_enable_join\": true,\n" +

"\t\t\"allow_unmute_self\": true,\n" +

"\t\t\"play_ivr_on_leave\": false,\n" +

"\t\t\"play_ivr_on_join\": false,\n" +

"\t\t\"allow_in_before_host\": true,\n" +

"\t\t\"auto_in_waiting_room\": false,\n" +

"\t\t\"allow_screen_shared_watermark\": false,\n" +

"\t\t\"only_enterprise_user_allowed\": false\n" +

"\t\t\n" +

"\t},\n" +

"\t\"meeting_type\": 0,\n" +

"\t\"enable_live\": false\n" +

"}";

Settings settings=new Settings();

settings.setMute_enable_join(true);

settings.setAllow_in_before_host(true);

settings.setAllow_screen_shared_watermark(false);

settings.setAllow_unmute_self(true);

settings.setPlay_ivr_on_join(false);

settings.setPlay_ivr_on_leave(false);

settings.setAuto_in_waiting_room(false);

settings.setOnly_enterprise_user_allowed(false);

settings.setAuto_record_type("local");

MeetingData meetingData=new MeetingData();

// 发起人的id

meetingData.setUserid("9d2fbf1c-4376-838a-4d06-ada53d20915b");

meetingData.setInstanceid(1);

meetingData.setSubject("已发起对"+companyName+"的会议~");

meetingData.setType(0);

meetingData.setInvitees(invitees);

meetingData.setStart_time(st);

meetingData.setEnd_time(et);

meetingData.setSettings(settings);

meetingData.setMeeting_type(0);

meetingData.setEnable_live(false);

// 创建ObjectMapper对象

ObjectMapper objectMapper = new ObjectMapper();

// 使用ObjectMapper将Java对象转换为JSON字符串

String json = objectMapper.writeValueAsString(meetingData);

// 创建会议室

String s = sendRequest("POST",address, "/v1/meetings", json);

// String s2 = sendRequest("POST",address, "/v1/meetings/4583169091896823219/invitees", data2);

JSONObject jsonObject = JSONObject.parseObject(s);

JSONArray meetingInfoList = jsonObject.getJSONArray("meeting_info_list");

Map objectMap = (Map) meetingInfoList.get(0);

String string1 = (String) objectMap.get("join_url");

// System.out.println(jsonObject);

return string1;

}

}

返回结果

ps 通过join_url可调起腾讯会议并入会

{

"meeting_number": 1,

"meeting_info_list":[

{

"subject": "tester's meeting",

"meeting_id": "433471464134410364",

"meeting_code": "637228110",

"password": "1111",

"host_key":"168168",

"start_time" : "1572172200",

"end_time" : "1572175800",

"hosts":["test1"],

"participants":[],

"join_url": "https://wemeet.qq.com/w/5oxCqAc",

"settings":{

"mute_enable_type_join": 2

},

"enable_live":true,

"live_config":{

"live_addr":"https://meeting.tencent.com/l/ehewFSfengBPae"

}

}

]

}

4.实时轨迹绘制

ps:主要是用到了websocket实现实时监听绘制,关于websocket可参考我的另一篇文章 https://www.cnblogs.com/sy2022/p/17611638.html

4.1 注册websocket

const socketUrl = "ws://localhost:2000/websocket/admin";

console.log(socketUrl);

if(this.socket!=null){

this.socket.close();

this.socket=null;

}

this.socket = new WebSocket(socketUrl);

//打开事件

this.socket.onopen = () => {

console.log("websocket已打开");

this.open2();

};

4.1 接收后台传过来的点位数据,并完成轨迹绘制。

this.socket.onmessage = (event) => {

const receivedData = JSON.parse(event.data);

// 获取数据中的id字段

const id = receivedData.id;

// 检查是否已经存在具有相同id的轨迹数组

if (!idToPolylinesMap[id]) {

idToPolylinesMap[id] = [];

}

console.log("收到WebSocket消息:", receivedData);

idToPolylinesMap[id].push(new BMap.Point(receivedData.longitude, receivedData.latitude));

// 创建线条对象

var polyline = new BMap.Polyline(idToPolylinesMap[id], polylineOptions);

// 将线条添加到地图

this.map.addOverlay(polyline);

// 此时,receivedData 是您从后端发送的 map 对象的 JavaScript 表示

// 您可以在这里处理它,访问其中的属性和值

};

4.2 开启关闭轨迹监听

开启

// 开启绘制点位信息

beginFlow(){

var self = this;

const socketUrl = "ws://localhost:2000/websocket/admin";

console.log(socketUrl);

if(this.socket!=null){

this.socket.close();

this.socket=null;

}

this.socket = new WebSocket(socketUrl);

//打开事件

this.socket.onopen = () => {

console.log("websocket已打开");

this.open2();

};

this.socket.onmessage = (event) => {

const receivedData = JSON.parse(event.data);

// 获取数据中的id字段

const id = receivedData.id;

// 检查是否已经存在具有相同id的轨迹数组

if (!idToPolylinesMap[id]) {

idToPolylinesMap[id] = [];

}

console.log("收到WebSocket消息:", receivedData);

idToPolylinesMap[id].push(new BMap.Point(receivedData.longitude, receivedData.latitude));

// 创建线条对象

var polyline = new BMap.Polyline(idToPolylinesMap[id], polylineOptions);

// 将线条添加到地图

this.map.addOverlay(polyline);

// 此时,receivedData 是您从后端发送的 map 对象的 JavaScript 表示

// 您可以在这里处理它,访问其中的属性和值

};

//关闭事件

this.socket.onclose = function() {

console.log("websocket已关闭");

};

//发生了错误事件

this.socket.onerror = function() {

console.log("websocket发生了错误");

}

},

关闭

closFlow(){

if(this.socket!=null){

this.socket.close();

this.socket=null;

}

this.open1();

},

效果如下

5.代码

前端

Baidu Map

危化品企业一览

开启轨迹监听

关闭轨迹监听

{{ item.companyName }}

Copyright © 2088 中国举办世界杯_世界杯足球场地尺寸 - lchjdj.com All Rights Reserved.
友情链接