fix:高德地图探索

This commit is contained in:
zhugongping 2026-01-24 11:52:06 +08:00
parent 652a32b797
commit 9bc4889309
18 changed files with 23327 additions and 704 deletions

View File

@ -18,7 +18,7 @@ spring:
master: master:
type: com.alibaba.druid.pool.DruidDataSource type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 url: jdbc:mysql://1.116.124.85:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root username: root
password: fastbee password: fastbee
druid: druid:
@ -56,7 +56,7 @@ spring:
# ds0: # 配置 mysql 数据源 # ds0: # 配置 mysql 数据源
# type: com.alibaba.druid.pool.DruidDataSource # type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver # driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 # url: jdbc:mysql://1.116.124.85:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# username: root # username: root
# password: fastbee # password: fastbee
# filters: stat,wall # filters: stat,wall
@ -100,9 +100,9 @@ spring:
# redis 配置 # redis 配置
redis: redis:
host: localhost # 地址 host: 1.116.124.85 # 地址
port: 6379 # 端口,默认为6379 port: 6379 # 端口,默认为6379
database: 3 # 数据库索引 database: 0 # 数据库索引
password: fastbee # 密码 password: fastbee # 密码
timeout: 10s # 连接超时时间 timeout: 10s # 连接超时时间
lettuce: lettuce:
@ -115,7 +115,7 @@ spring:
mqtt: mqtt:
username: fastbee # 账号 username: fastbee # 账号
password: fastbee # 密码 password: fastbee # 密码
host-url: tcp://localhost:1883 # mqtt连接tcp地址 host-url: tcp://1.116.124.85:1883 # mqtt连接tcp地址
client-id: ${random.int} # 客户端Id,不能相同,采用随机数 ${random.value} client-id: ${random.int} # 客户端Id,不能相同,采用随机数 ${random.value}
default-topic: test # 默认主题 default-topic: test # 默认主题
timeout: 30 # 超时时间 timeout: 30 # 超时时间
@ -123,7 +123,7 @@ spring:
clearSession: true # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) clearSession: true # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息)
# EMQX API配置需要在运行emqx之后去EMQX管理后台手动创建API Key和Secret,并配置到application.yml中 # EMQX API配置需要在运行emqx之后去EMQX管理后台手动创建API Key和Secret,并配置到application.yml中
emqx: emqx:
host: localhost # EMQX服务器地址 host: 1.116.124.85 # EMQX服务器地址
port: 18083 port: 18083
ApiKey: emqx.key.123 # EMQX API Key ApiKey: emqx.key.123 # EMQX API Key
ApiSecret: emqx.api.123 ApiSecret: emqx.api.123

View File

@ -72,7 +72,7 @@ public class HttpUtils {
} }
log.info("recv - {}", result); log.info("recv - {}", result);
} catch (ConnectException e) { } catch (ConnectException e) {
log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); log.error("调用HttpUtils.sendGet ConnectException, url={},param={}", url, param, e);
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
} catch (IOException e) { } catch (IOException e) {
@ -123,13 +123,13 @@ public class HttpUtils {
} }
log.info("recv - {}", result); log.info("recv - {}", result);
} catch (ConnectException e) { } catch (ConnectException e) {
log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); log.error("调用HttpUtils.sendPost ConnectException, url={},param={}", url, param, e);
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); log.error("调用HttpUtils.sendPost SocketTimeoutException, url={},param={}", url, param, e);
} catch (IOException e) { } catch (IOException e) {
log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); log.error("调用HttpUtils.sendPost IOException, url={},param={}", url, param, e);
} catch (Exception e) { } catch (Exception e) {
log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); log.error("调用HttpsUtil.sendPost Exception, url={},param={}", url, param, e);
} finally { } finally {
try { try {
if (out != null) { if (out != null) {
@ -139,7 +139,7 @@ public class HttpUtils {
in.close(); in.close();
} }
} catch (IOException ex) { } catch (IOException ex) {
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); log.error("调用in.close Exception, url={},param={}", url, param, ex);
} }
} }
return result.toString(); return result.toString();
@ -177,13 +177,13 @@ public class HttpUtils {
conn.disconnect(); conn.disconnect();
br.close(); br.close();
} catch (ConnectException e) { } catch (ConnectException e) {
log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); log.error("调用HttpUtils.sendSSLPost ConnectException, url={},param={}", url, param, e);
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url={},param={}", url, param, e);
} catch (IOException e) { } catch (IOException e) {
log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); log.error("调用HttpUtils.sendSSLPost IOException, url={},param={}", url, param, e);
} catch (Exception e) { } catch (Exception e) {
log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); log.error("调用HttpsUtil.sendSSLPost Exception, url={},param={}", url, param, e);
} }
return result.toString(); return result.toString();
} }
@ -213,13 +213,13 @@ public class HttpUtils {
} }
log.info("recv - {}", result); log.info("recv - {}", result);
} catch (ConnectException e) { } catch (ConnectException e) {
log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + json, e); log.error("调用HttpUtils.sendPost ConnectException, url={},param={}", url, json, e);
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + json, e); log.error("调用HttpUtils.sendPost SocketTimeoutException, url={},param={}", url, json, e);
} catch (IOException e) { } catch (IOException e) {
log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + json, e); log.error("调用HttpUtils.sendPost IOException, url={},param={}", url, json, e);
} catch (Exception e) { } catch (Exception e) {
log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + json, e); log.error("调用HttpsUtil.sendPost Exception, url={},param={}", url, json, e);
} finally { } finally {
try { try {
if (out != null) { if (out != null) {

View File

@ -132,7 +132,7 @@ public class DevicePropFetchConsumer {
modbusJob.setStatus("0"); modbusJob.setStatus("0");
List<ModbusJob> modbusJobList = modbusJobService.selectModbusJobList(modbusJob); List<ModbusJob> modbusJobList = modbusJobService.selectModbusJobList(modbusJob);
if (CollectionUtils.isEmpty(modbusJobList)) return modbusPollMsg; if (CollectionUtils.isEmpty(modbusJobList)) return modbusPollMsg;
//处理子设备情况 // 处理子设备情况
handleSubDeviceStatus(modbusJobList); handleSubDeviceStatus(modbusJobList);
List<String> commandList = modbusJobList.stream().map(ModbusJob::getCommand).collect(Collectors.toList()); List<String> commandList = modbusJobList.stream().map(ModbusJob::getCommand).collect(Collectors.toList());

View File

@ -43,7 +43,7 @@ public class DeviceJobServiceImpl implements IDeviceJobService {
/** /**
* 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
*/ */
@PostConstruct //@PostConstruct
public void init() throws SchedulerException, TaskException { public void init() throws SchedulerException, TaskException {
scheduler.clear(); scheduler.clear();
// 设备定时任务 // 设备定时任务

View File

@ -47,7 +47,6 @@ public class DeviceOtherMsgHandler {
* @param bo * @param bo
*/ */
public void messageHandler(DeviceReportBo bo) { public void messageHandler(DeviceReportBo bo) {
String type = "";
String name = topicsUtils.parseTopicName(bo.getTopicName()); String name = topicsUtils.parseTopicName(bo.getTopicName());
if (StringUtils.isEmpty(name) || name.endsWith(TopicType.FUNCTION_GET.getTopicSuffix())) return; if (StringUtils.isEmpty(name) || name.endsWith(TopicType.FUNCTION_GET.getTopicSuffix())) return;
ReportDataBo data = this.buildReportData(bo); ReportDataBo data = this.buildReportData(bo);

View File

@ -43,10 +43,10 @@ public class MessageEncoderWrapper extends ChannelOutboundHandlerAdapter {
} }
output = null; output = null;
} catch (EncoderException e) { } catch (EncoderException e) {
log.error("消息编码异常" + packet.message, e); log.error("消息编码异常{}", packet.message, e);
throw e; throw e;
} catch (Throwable e) { } catch (Throwable e) {
log.error("消息编码异常" + packet.message, e); log.error("消息编码异常{}", packet.message, e);
throw new EncoderException(e); throw new EncoderException(e);
} finally { } finally {
if (output != null) { if (output != null) {

View File

@ -133,7 +133,7 @@ public class RegisterReqHandler extends ReqAbstractHandler implements Initializi
rPort = viaHeader.getPort(); rPort = viaHeader.getPort();
} }
SipDevice device = new SipDevice(); SipDevice device = new SipDevice();
;
device.setStreammode("UDP"); device.setStreammode("UDP");
device.setDeviceSipId(sipId); device.setDeviceSipId(sipId);
device.setIp(received); device.setIp(received);
@ -156,7 +156,7 @@ public class RegisterReqHandler extends ReqAbstractHandler implements Initializi
sipSender.transmitRequest(Sipreq.getLocalAddress().getHostAddress(), response); sipSender.transmitRequest(Sipreq.getLocalAddress().getHostAddress(), response);
// 注册成功 // 注册成功
if (registerFlag == 1) { if (registerFlag == 1) {
log.info("注册成功! sipId:" + device.getDeviceSipId()); log.info("注册成功! sipId:{}", device.getDeviceSipId());
device.setRegistertime(DateUtils.getNowDate()); device.setRegistertime(DateUtils.getNowDate());
sipDeviceService.updateDevice(device); sipDeviceService.updateDevice(device);
List<SipDeviceChannel> channels = sipDeviceChannelService.selectSipDeviceChannelByDeviceSipId(device.getDeviceSipId()); List<SipDeviceChannel> channels = sipDeviceChannelService.selectSipDeviceChannelByDeviceSipId(device.getDeviceSipId());
@ -166,7 +166,7 @@ public class RegisterReqHandler extends ReqAbstractHandler implements Initializi
//添加到统一设备管理 默认添加到admin账号下 //添加到统一设备管理 默认添加到admin账号下
int result = deviceService.insertDeviceAuto(device.getDeviceSipId(), 1L, sipConfig.getProductId()); int result = deviceService.insertDeviceAuto(device.getDeviceSipId(), 1L, sipConfig.getProductId());
if (result == 1) { if (result == 1) {
log.info("-----------sip设备认证成功,并自动添加设备到系统,SipId:" + device.getDeviceSipId() + "---------------"); log.info("-----------sip设备认证成功,并自动添加设备到系统,SipId:{}---------------", device.getDeviceSipId());
} }
iotdev = new Device(); iotdev = new Device();
} }
@ -177,10 +177,10 @@ public class RegisterReqHandler extends ReqAbstractHandler implements Initializi
// 重新注册更新设备和通道,以免设备替换或更新后信息无法更新 // 重新注册更新设备和通道,以免设备替换或更新后信息无法更新
onRegister(device); onRegister(device);
} else { } else {
log.info("未找到改设备! deviceId:" + device.getDeviceId()); log.info("未找到改设备! deviceId:{}", device.getDeviceId());
} }
} else if (registerFlag == 2) { } else if (registerFlag == 2) {
log.info("注销成功! deviceId:" + device.getDeviceId()); log.info("注销成功! deviceId:{}", device.getDeviceId());
mqttService.publishStatus(device, 4); mqttService.publishStatus(device, 4);
} }

View File

@ -0,0 +1,23 @@
package com.fastbee.iot.model.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 坐标点
* @Date: 2026-01-23
* @Author: zhugongping
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Position {
@ApiModelProperty("经度")
private double lng; // longitude
@ApiModelProperty("纬度")
private double lat; // latitude
}

View File

@ -46,6 +46,7 @@ import com.fastbee.iot.model.gateWay.ProductSubGatewayVO;
import com.fastbee.iot.model.gateWay.SubDeviceListVO; import com.fastbee.iot.model.gateWay.SubDeviceListVO;
import com.fastbee.iot.service.*; import com.fastbee.iot.service.*;
import com.fastbee.iot.tdengine.service.ILogService; import com.fastbee.iot.tdengine.service.ILogService;
import com.fastbee.iot.util.CoordinateTransform;
import com.fastbee.system.mapper.SysDeptMapper; import com.fastbee.system.mapper.SysDeptMapper;
import com.fastbee.system.service.ISysUserService; import com.fastbee.system.service.ISysUserService;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
@ -1581,9 +1582,10 @@ public class DeviceServiceImpl implements IDeviceService {
/** /**
* 根据IP获取地址 * 根据IP获取地址
* * 流程是:
* @param ip * 调用太平洋网络IP查询接口,根据IP获取城市名称
* @return * 调用百度地图地理编码API,根据城市名称获取经纬度
* @param ip IP
*/ */
private void setLocation(String ip, Device device) { private void setLocation(String ip, Device device) {
String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
@ -1607,9 +1609,10 @@ public class DeviceServiceImpl implements IDeviceService {
} }
/** /**
* 设置经纬度 * 根据地理编码获取经纬度
* 调用百度地理编码获取的是BD09
* *
* @param city * @param city 地理编码,如:北京市海淀区上地十街10号
*/ */
private void setLatitudeAndLongitude(String city, Device device) throws UnsupportedEncodingException { private void setLatitudeAndLongitude(String city, Device device) throws UnsupportedEncodingException {
String BAIDU_URL = "http://api.map.baidu.com/geocoding/v3/"; String BAIDU_URL = "http://api.map.baidu.com/geocoding/v3/";
@ -1620,8 +1623,10 @@ public class DeviceServiceImpl implements IDeviceService {
if (!StringUtils.isEmpty(baiduResponse)) { if (!StringUtils.isEmpty(baiduResponse)) {
JSONObject baiduObject = JSONObject.parseObject(baiduResponse); JSONObject baiduObject = JSONObject.parseObject(baiduResponse);
JSONObject location = baiduObject.getJSONObject("result").getJSONObject("location"); JSONObject location = baiduObject.getJSONObject("result").getJSONObject("location");
device.setLongitude(location.getBigDecimal("lng")); // 将 BD09转为GCJ02 (百度坐标系转火星坐标系)
device.setLatitude(location.getBigDecimal("lat")); double[] gcj02 = CoordinateTransform.bd09ToGcj02(location.getDouble("lng"), location.getDouble("lat"));
device.setLongitude(BigDecimal.valueOf(gcj02[0]));
device.setLatitude(BigDecimal.valueOf(gcj02[1]));
System.out.println(device.getSerialNumber() + "- 设置经度:" + location.getBigDecimal("lng") + ",设置纬度:" System.out.println(device.getSerialNumber() + "- 设置经度:" + location.getBigDecimal("lng") + ",设置纬度:"
+ location.getBigDecimal("lat")); + location.getBigDecimal("lat"));
} }

View File

@ -318,13 +318,13 @@ public class ToolServiceImpl implements IToolService {
if (productModel.getStatus() == 2) { if (productModel.getStatus() == 2) {
return returnUnauthorized(mqttModel, "设备简单认证,设备处于禁用状态"); return returnUnauthorized(mqttModel, "设备简单认证,设备处于禁用状态");
} }
log.info("-----------设备简单认证成功,clientId:" + mqttModel.getClientId() + "---------------"); log.info("-----------设备简单认证成功,clientId:{}---------------", mqttModel.getClientId());
return ResponseEntity.ok().body("ok"); return ResponseEntity.ok().body("ok");
} else { } else {
// 自动添加设备 // 自动添加设备
int result = deviceService.insertDeviceAuto(mqttModel.getDeviceNumber(), mqttModel.getUserId(), mqttModel.getProductId()); int result = deviceService.insertDeviceAuto(mqttModel.getDeviceNumber(), mqttModel.getUserId(), mqttModel.getProductId());
if (result == 1) { if (result == 1) {
log.info("-----------设备简单认证成功,并自动添加设备到系统,clientId:" + mqttModel.getClientId() + "---------------"); log.info("-----------设备简单认证成功,并自动添加设备到系统,clientId:{}---------------", mqttModel.getClientId());
return ResponseEntity.ok().body("ok"); return ResponseEntity.ok().body("ok");
} }
return returnUnauthorized(mqttModel, "设备简单认证,自动添加设备失败"); return returnUnauthorized(mqttModel, "设备简单认证,自动添加设备失败");

View File

@ -0,0 +1,223 @@
package com.fastbee.iot.util;
/**
* 坐标系转换工具类(WGS-84 ⇄ GCJ-02 ⇄ BD-09)
* 基于 double 类型实现,适用于 LBS、地图开发等场景
* 精度:约 1~10 米级
* GPS 是 WGS-84
* 国内地图要 GCJ-02(火星坐标系)
* 百度再加一层 BD-09
* OSM 坚持用 WGS-84
* @Date: 2026-01-23
* @Author: zhugongping
**/
public class CoordinateTransform {
// ====================== 常量定义 ======================
private static final double PI = Math.PI;
private static final double A = 6378245.0; // 长半轴 (km)
private static final double EE = 0.00669342162296594323; // 扁率平方
/**
* 判断是否在国内(粗略判断,用于避免国外偏移)
* @param lng 经度
* @param lat 纬度
* @return true:国内;false:国外
*/
private static boolean inOfChina(double lng, double lat) {
return (lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55);
}
// 弧度转角度
private static double radToDeg(double rad) {
return rad * 180.0 / PI;
}
// 角度转弧度
private static double degToRad(double deg) {
return deg * PI / 180.0;
}
// 求两点间距离(简化球面距离,单位:米)
private static double distance(double lng1, double lat1, double lng2, double lat2) {
double dLat = degToRad(lat2 - lat1);
double dLng = degToRad(lng2 - lng1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(degToRad(lat1)) * Math.cos(degToRad(lat2)) *
Math.sin(dLng / 2) * Math.sin(dLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return A * c; // A 是地球半径(米)
}
// ==================== WGS-84 → GCJ-02 ====================
/**
* WGS-84 转 GCJ-02(火星坐标系)
*/
public static double[] wgs84ToGcj02(double wgsLng, double wgsLat) {
if (!inOfChina(wgsLng, wgsLat)) {
return new double[]{wgsLng, wgsLat};
}
double dLat = transformLat(wgsLng - 105.0, wgsLat - 35.0);
double dLng = transformLng(wgsLng - 105.0, wgsLat - 35.0);
double radLat = degToRad(wgsLat);
double magic = Math.sin(radLat);
magic = 1 - EE * magic * magic;
double sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * PI);
dLng = (dLng * 180.0) / (A / sqrtMagic * Math.cos(radLat) * PI);
return new double[]{wgsLng + dLng, wgsLat + dLat};
}
// ==================== GCJ-02 → WGS-84 ====================
/**
* GCJ-02 转 WGS-84(近似还原,存在微小误差)
*/
public static double[] gcj02ToWgs84(double gcjLng, double gcjLat) {
if (!inOfChina(gcjLng, gcjLat)) {
return new double[]{gcjLng, gcjLat};
}
double[] gps = wgs84ToGcj02(gcjLng, gcjLat);
double dLng = gps[0] - gcjLng;
double dLat = gps[1] - gcjLat;
return new double[]{gcjLng - dLng, gcjLat - dLat};
}
// ==================== GCJ-02 → BD-09 ====================
/**
* GCJ-02 转 BD-09(百度坐标系)
*/
public static double[] gcj02ToBd09(double gcjLng, double gcjLat) {
double z = Math.sqrt(gcjLng * gcjLng + gcjLat * gcjLat) + 0.00002 * Math.sin(gcjLat * PI);
double theta = Math.atan2(gcjLat, gcjLng) + 0.000003 * Math.cos(gcjLng * PI);
double bdLng = z * Math.cos(theta) + 0.0065;
double bdLat = z * Math.sin(theta) + 0.006;
return new double[]{bdLng, bdLat};
}
// ==================== BD-09 → GCJ-02 ====================
/**
* BD-09 转 GCJ-02
*/
public static double[] bd09ToGcj02(double bdLng, double bdLat) {
double x = bdLng - 0.0065;
double y = bdLat - 0.006;
double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * PI);
double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * PI);
double gcjLng = z * Math.cos(theta);
double gcjLat = z * Math.sin(theta);
return new double[]{gcjLng, gcjLat};
}
// ==================== WGS-84 ⇄ BD-09 快捷转换 ====================
/**
* WGS-84 转 BD-09(先转 GCJ-02,再转 BD-09)
*/
public static double[] wgs84ToBd09(double wgsLng, double wgsLat) {
double[] gcj = wgs84ToGcj02(wgsLng, wgsLat);
return gcj02ToBd09(gcj[0], gcj[1]);
}
/**
* BD-09 转 WGS-84(先转 GCJ-02,再转 WGS-84)
*/
public static double[] bd09ToWgs84(double bdLng, double bdLat) {
double[] gcj = bd09ToGcj02(bdLng, bdLat);
return gcj02ToWgs84(gcj[0], gcj[1]);
}
// ==================== 内部辅助函数 ====================
private static double transformLat(double lng, double lat) {
double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;
return ret;
}
private static double transformLng(double lng, double lat) {
double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;
return ret;
}
// ==================== 工具方法 ====================
/**
* 格式化坐标输出(保留6位小数)
*/
public static String formatCoord(double lng, double lat) {
return String.format("%.6f, %.6f", lng, lat);
}
/**
* 四舍五入到指定位数
*/
public static double round(double value, int scale) {
double factor = Math.pow(10, scale);
return Math.round(value * factor) / factor;
}
/**
* 打印转换结果(调试用)
*/
public static void printConvert(String fromType, double fromLng, double fromLat,
String toType, double[] result) {
System.out.printf("%s [%.6f, %.6f] → %s [%s]%n",
fromType, fromLng, fromLat,
toType, formatCoord(result[0], result[1]));
}
public static void main(String[] args) {
// 北京天安门 GPS 坐标(WGS-84)116.397428,39.90923
// 长沙市政府 BD-09 112.945473,28.234889
// 岩安测控 WGS-84 112.862816,28.125462
double wgsLng = 112.862816;
double wgsLat = 28.125462;
System.out.println("是否在国内 = " + inOfChina(wgsLng, wgsLat));
System.out.println("=== 坐标系转换测试 ===");
// WGS-84 → GCJ-02 111.94760023918425,28.812219998012168
double[] gcj = CoordinateTransform.wgs84ToGcj02(wgsLng, wgsLat);
CoordinateTransform.printConvert("WGS-84", wgsLng, wgsLat, "GCJ-02", gcj);
// GCJ-02 → BD-09
double[] bd = CoordinateTransform.gcj02ToBd09(gcj[0], gcj[1]);
CoordinateTransform.printConvert("GCJ-02", gcj[0], gcj[1], "BD-09", bd);
// BD-09 → GCJ-02
double[] gcjBack = CoordinateTransform.bd09ToGcj02(112.945473,28.234889);
CoordinateTransform.printConvert("BD-09", bd[0], bd[1], "GCJ-02", gcjBack);
// GCJ-02 → WGS-84 111.944921,28.809167
double[] wgsBack = CoordinateTransform.gcj02ToWgs84(111.944921,28.809167);
CoordinateTransform.printConvert("GCJ-02", gcjBack[0], gcjBack[1], "WGS-84", wgsBack);
// 直接计算 WGS-84 → BD-09
double[] directBd = CoordinateTransform.wgs84ToBd09(wgsLng, wgsLat);
CoordinateTransform.printConvert("WGS-84", wgsLng, wgsLat, "BD-09(Direct)", directBd);
// 计算偏移距离
double offset = CoordinateTransform.distance(wgsLng, wgsLat, gcj[0], gcj[1]);
System.out.printf("WGS-84 与 GCJ-02 偏移距离:%.2f 米%n", offset);
}
}

23428
vue/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -44,8 +44,10 @@
"axios": "0.24.0", "axios": "0.24.0",
"clipboard": "2.0.8", "clipboard": "2.0.8",
"codemirror": "^5.65.2", "codemirror": "^5.65.2",
"coordtransform": "^2.1.2",
"core-js": "3.25.3", "core-js": "3.25.3",
"echarts": "^5.4.0", "echarts": "^5.4.0",
"echarts-extension-leaflet": "^1.2.2",
"echarts-liquidfill": "^3.0.6", "echarts-liquidfill": "^3.0.6",
"element-china-area-data": "^4.1.1", "element-china-area-data": "^4.1.1",
"element-ui": "2.15.10", "element-ui": "2.15.10",
@ -62,6 +64,7 @@
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"jsonlint": "^1.6.3", "jsonlint": "^1.6.3",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"leaflet": "^1.9.4",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"minimatch": "^3.1.2", "minimatch": "^3.1.2",
"moment": "^2.29.4", "moment": "^2.29.4",

View File

@ -1,28 +1,34 @@
// 使用OpenStreetMap和Leaflet替代百度地图
export function loadBMap() { export function loadBMap() {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
if (typeof BMap !== 'undefined') { // eslint-disable-next-line no-undef
resolve(BMap) if (typeof L !== 'undefined') {
return true // eslint-disable-next-line no-undef
resolve(L);
return true;
} }
window.onBMapCallback = function() {
resolve(BMap) // 加载Leaflet CSS
if (!document.querySelector('link[href*="leaflet.css"]')) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
link.integrity = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=';
link.crossOrigin = '';
document.head.appendChild(link);
} }
// 使用https协议需要添加一下meta标签
var protocolStr = document.location.protocol; // 加载Leaflet JS
if(protocolStr == "https:") const script = document.createElement('script');
{ script.type = 'text/javascript';
let meta = document.createElement('meta') script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
meta.httpEquiv = 'Content-Security-Policy' script.integrity = 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=';
meta.content ='upgrade-insecure-requests' script.crossOrigin = '';
meta.onerror = reject script.onload = function() {
document.head.appendChild(meta) // eslint-disable-next-line no-undef
} resolve(L);
// 引入百度地图 };
let script = document.createElement('script') script.onerror = reject;
script.type = 'text/javascript' document.head.appendChild(script);
script.src = });
'http://api.map.baidu.com/api?v=2.0&ak='+ process.env.VUE_APP_BAI_DU_AK +'&s=1&__ec_v__=20190126&callback=onBMapCallback'
script.onerror = reject
document.head.appendChild(script)
})
} }

View File

@ -319,10 +319,9 @@ import axios from 'axios';
import { getDeviceStatistic } from '@/api/iot/device'; import { getDeviceStatistic } from '@/api/iot/device';
import { listNotice, getNotice } from '@/api/system/notice'; import { listNotice, getNotice } from '@/api/system/notice';
import CountTo from 'vue-count-to'; import CountTo from 'vue-count-to';
import { loadBMap } from '@/utils/map.js'; import 'leaflet/dist/leaflet.css';
//安装的是echarts完整包,里面包含百度地图扩展,路径为 echarts/extension/bmap/bmap.js,将其引入 import 'echarts-extension-leaflet';
//ECharts的百度地图扩展,可以在百度地图上展现点图,线图,热力图等可视化 import coordtransform from 'coordtransform';
// 注意:bmap扩展需要在BMap加载后再引入
import { getServer } from '@/api/monitor/server'; import { getServer } from '@/api/monitor/server';
import { listAllDeviceShort } from '@/api/iot/device'; import { listAllDeviceShort } from '@/api/iot/device';
import 'echarts-liquidfill'; import 'echarts-liquidfill';
@ -1157,23 +1156,10 @@ export default {
/**加载地图*/ /**加载地图*/
loadMap() { loadMap() {
this.$nextTick(() => { this.$nextTick(() => {
loadBMap().then(() => { // 使用 echarts-extension-leaflet,已在 import 时加载
if (typeof BMap === 'undefined') { setTimeout(() => {
this.$message.error('百度地图API未正确加载'); this.getmap();
return; }, 300);
}
// 确保bmap扩展已加载
try {
require('echarts/extension/bmap/bmap');
} catch (e) {
// 忽略扩展加载错误
}
setTimeout(() => {
this.getmap();
}, 500);
}).catch((error) => {
this.$message.error('百度地图加载失败,请检查API Key配置');
});
}); });
}, },
/** 查询服务器信息 */ /** 查询服务器信息 */
@ -1229,13 +1215,14 @@ export default {
/** 地图 */ /** 地图 */
getmap() { getmap() {
if (typeof BMap === 'undefined') { if (!this.$refs.map) {
this.$message.error('百度地图API未正确加载,请刷新页面重试');
return; return;
} }
if (!this.$refs.map) { // 销毁旧的ECharts实例,避免多个实例叠加
return; var existingChart = this.$echarts.getInstanceByDom(this.$refs.map);
if (existingChart) {
existingChart.dispose();
} }
var myChart = this.$echarts.init(this.$refs.map); var myChart = this.$echarts.init(this.$refs.map);
@ -1258,8 +1245,14 @@ export default {
let convertData = function (data, status) { let convertData = function (data, status) {
var res = []; var res = [];
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
var geoCoord = [data[i].longitude, data[i].latitude]; // 验证经纬度有效性
if (geoCoord && data[i].status == status) { var lng = parseFloat(data[i].longitude);
var lat = parseFloat(data[i].latitude);
if (isNaN(lng) || isNaN(lat) || lng === 0 || lat === 0) {
continue;
}
var geoCoord = [lng, lat];
if (data[i].status == status) {
res.push({ res.push({
name: data[i].deviceName, name: data[i].deviceName,
value: geoCoord, value: geoCoord,
@ -1320,14 +1313,9 @@ export default {
return { center: [centerLng, centerLat], zoom: zoom }; return { center: [centerLng, centerLat], zoom: zoom };
}; };
// 使用长沙作为默认中心点,如果有设备数据则使用计算的中心点 // 使用中国长沙作为地图中心点
let mapConfig = { center: [112.867037, 28.11857], zoom: 12 }; // 长沙坐标:经度 112.94, 纬度 28.23
if (this.deviceList && this.deviceList.length > 0) { let mapConfig = { center: [112.94, 28.23], zoom: 5 };
const calculated = calculateMapCenter(this.deviceList);
if (calculated.center && calculated.center[0] > 0) {
mapConfig = calculated;
}
}
option = { option = {
title: { title: {
@ -1382,21 +1370,27 @@ export default {
return htmlStr; return htmlStr;
}, },
}, },
bmap: { leaflet: {
center: mapConfig.center, // echarts-extension-leaflet center 使用 [lng, lat] 格式
zoom: mapConfig.zoom, // 长沙坐标:经度 112.94, 纬度 28.23
center: [112.94, 28.23],
zoom: 5,
roam: true, roam: true,
// 确保地图正确初始化 tiles: [
enableMapClick: true, {
// 暂时不使用自定义样式,确保地图能正常显示 // 使用高德地图瓦片(国内访问稳定)
// mapStyle: { urlTemplate: 'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
// styleJson: [...] options: {
// }, subdomains: ['1', '2', '3', '4'],
attribution: '&copy; 高德地图',
},
},
],
}, },
series: [ series: [
{ {
type: 'scatter', type: 'scatter',
coordinateSystem: 'bmap', coordinateSystem: 'leaflet',
data: convertData(this.deviceList, 1), data: convertData(this.deviceList, 1),
symbolSize: 15, symbolSize: 15,
itemStyle: { itemStyle: {
@ -1405,7 +1399,7 @@ export default {
}, },
{ {
type: 'scatter', type: 'scatter',
coordinateSystem: 'bmap', coordinateSystem: 'leaflet',
data: convertData(this.deviceList, 2), data: convertData(this.deviceList, 2),
symbolSize: 15, symbolSize: 15,
itemStyle: { itemStyle: {
@ -1414,7 +1408,7 @@ export default {
}, },
{ {
type: 'scatter', type: 'scatter',
coordinateSystem: 'bmap', coordinateSystem: 'leaflet',
data: convertData(this.deviceList, 4), data: convertData(this.deviceList, 4),
symbolSize: 15, symbolSize: 15,
itemStyle: { itemStyle: {
@ -1423,7 +1417,7 @@ export default {
}, },
{ {
type: 'effectScatter', type: 'effectScatter',
coordinateSystem: 'bmap', coordinateSystem: 'leaflet',
data: convertData(this.deviceList, 3), data: convertData(this.deviceList, 3),
symbolSize: 15, symbolSize: 15,
showEffectOn: 'render', showEffectOn: 'render',
@ -1465,35 +1459,10 @@ export default {
// 设置地图配置 // 设置地图配置
myChart.setOption(option, true); myChart.setOption(option, true);
// 等待地图初始化后调整大小和刷新 // 等待地图初始化完成
setTimeout(() => { this.$nextTick(() => {
myChart.resize(); myChart.resize();
});
const bmapModel = myChart.getModel().getComponent('bmap');
if (bmapModel && bmapModel.getBMap) {
const bmapInstance = bmapModel.getBMap();
if (bmapInstance) {
// 启用地图功能
bmapInstance.enableScrollWheelZoom(true);
bmapInstance.enableDoubleClickZoom(true);
bmapInstance.enableDragging(true);
// 设置中心点和缩放级别
const centerPoint = new BMap.Point(mapConfig.center[0], mapConfig.center[1]);
bmapInstance.setCenter(centerPoint);
bmapInstance.setZoom(mapConfig.zoom);
// 刷新地图确保瓦片加载
setTimeout(() => {
myChart.resize();
bmapInstance.reset();
bmapInstance.setCenter(centerPoint);
bmapInstance.setZoom(mapConfig.zoom);
myChart.resize();
}, 500);
}
}
}, 800);
} catch (error) { } catch (error) {
this.$message.error('地图初始化失败: ' + error.message); this.$message.error('地图初始化失败: ' + error.message);
} }

View File

@ -79,7 +79,7 @@
<script type="text/javascript"> <script type="text/javascript">
// 从环境变量获取AK(这里需要手动设置,因为这是静态HTML文件) // 从环境变量获取AK(这里需要手动设置,因为这是静态HTML文件)
// 实际项目中应该从 process.env.VUE_APP_BAI_DU_AK 获取 // 实际项目中应该从 process.env.VUE_APP_BAI_DU_AK 获取
const AK = 's9mUXN9a7bKgVu6U6Z3bXhKSxtz97YPI'; // 请替换为您的AK const AK = 'FN2xbG1m5q9iwNq1heNF9nBOb2uhv3Om'; // 请替换为您的AK
// 更新状态显示 // 更新状态显示
function updateStatus(elementId, text, className) { function updateStatus(elementId, text, className) {

View File

@ -1088,21 +1088,44 @@ export default {
}, },
// 地图定位 // 地图定位
getmap() { getmap() {
this.map = new BMap.Map('map'); // 清除旧地图实例
let point = null; if (this.map) {
if (this.form.longitude != null && this.form.longitude != '' && this.form.latitude != null && this.form.latitude != '') { this.map.remove();
point = new BMap.Point(this.form.longitude, this.form.latitude); this.map = null;
} else { }
point = new BMap.Point(116.404, 39.915);
// 使用Leaflet和高德地图
let lat, lng;
let parsedLat = parseFloat(this.form.latitude);
let parsedLng = parseFloat(this.form.longitude);
// 验证坐标有效性
if (!isNaN(parsedLat) && !isNaN(parsedLng) && parsedLat !== 0 && parsedLng !== 0) {
lat = parsedLat;
lng = parsedLng;
} else {
// 默认使用中国长沙坐标
lat = 28.23;
lng = 112.94;
}
// 创建地图
this.map = L.map('map').setView([lat, lng], 15);
// 添加高德地图图层(国内访问稳定)
L.tileLayer('https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', {
subdomains: ['1', '2', '3', '4'],
attribution: '&copy; 高德地图',
maxZoom: 18
}).addTo(this.map);
// 添加标记点
this.mk = L.marker([lat, lng]).addTo(this.map);
// 添加popup提示
if (this.form.deviceName) {
this.mk.bindPopup(`<b>${this.form.deviceName}</b><br>${this.form.networkAddress || ''}`).openPopup();
} }
this.map.centerAndZoom(point, 19);
this.map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
this.map.addControl(new BMap.NavigationControl());
// 标注设备位置
this.mk = new BMap.Marker(point);
this.map.addOverlay(this.mk);
this.map.panTo(point);
}, },
// 生成随机字母和数字 // 生成随机字母和数字
generateNum() { generateNum() {

View File

@ -1185,20 +1185,40 @@ export default {
}, },
// 地图定位 // 地图定位
getmap() { getmap() {
this.map = new BMap.Map('map'); // 清除旧地图实例
let point = null; if (this.map) {
if (this.form.longitude != null && this.form.longitude != '' && this.form.latitude != null && this.form.latitude != '') { this.map.remove();
point = new BMap.Point(this.form.longitude, this.form.latitude); this.map = null;
} else { }
point = new BMap.Point(116.404, 39.915);
// 使用Leaflet和OpenStreetMap
let lat, lng;
if (this.form.latitude != null && this.form.latitude != '' && this.form.longitude != null && this.form.longitude != '') {
lat = parseFloat(this.form.latitude);
lng = parseFloat(this.form.longitude);
} else {
// 默认使用北京天安门坐标
lat = 39.915;
lng = 116.404;
}
// 创建地图
this.map = L.map('map').setView([lat, lng], 15);
// 添加高德地图图层(国内访问稳定)
L.tileLayer('https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', {
subdomains: ['1', '2', '3', '4'],
attribution: '&copy; 高德地图',
maxZoom: 18
}).addTo(this.map);
// 添加标记点
this.mk = L.marker([lat, lng]).addTo(this.map);
// 添加popup提示
if (this.form.deviceName) {
this.mk.bindPopup(`<b>${this.form.deviceName}</b><br>${this.form.networkAddress || ''}`).openPopup();
} }
this.map.centerAndZoom(point, 19);
this.map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
this.map.addControl(new BMap.NavigationControl());
// 标注设备位置
this.mk = new BMap.Marker(point);
this.map.addOverlay(this.mk);
this.map.panTo(point);
}, },
// 生成随机字母和数字 // 生成随机字母和数字
generateNum() { generateNum() {