299 lines
7.7 KiB
JavaScript
299 lines
7.7 KiB
JavaScript
/**
|
||
* 电子围栏坐标点模型
|
||
* 用于存储围栏绘制过程中用户选定的经纬度坐标点
|
||
*/
|
||
|
||
const { DataTypes } = require('sequelize');
|
||
const { sequelize } = require('../config/database-simple');
|
||
const BaseModel = require('./BaseModel');
|
||
|
||
class ElectronicFencePoint extends BaseModel {
|
||
// 模型属性定义
|
||
static attributes = {
|
||
id: {
|
||
type: DataTypes.INTEGER,
|
||
primaryKey: true,
|
||
autoIncrement: true,
|
||
comment: '主键ID'
|
||
},
|
||
fence_id: {
|
||
type: DataTypes.INTEGER,
|
||
allowNull: false,
|
||
comment: '关联的围栏ID',
|
||
references: {
|
||
model: 'electronic_fences',
|
||
key: 'id'
|
||
}
|
||
},
|
||
point_order: {
|
||
type: DataTypes.INTEGER,
|
||
allowNull: false,
|
||
comment: '坐标点在围栏中的顺序(从0开始)'
|
||
},
|
||
longitude: {
|
||
type: DataTypes.DECIMAL(10, 7),
|
||
allowNull: false,
|
||
comment: '经度'
|
||
},
|
||
latitude: {
|
||
type: DataTypes.DECIMAL(10, 7),
|
||
allowNull: false,
|
||
comment: '纬度'
|
||
},
|
||
point_type: {
|
||
type: DataTypes.ENUM('corner', 'control', 'marker'),
|
||
allowNull: false,
|
||
defaultValue: 'corner',
|
||
comment: '坐标点类型:corner-拐角点,control-控制点,marker-标记点'
|
||
},
|
||
description: {
|
||
type: DataTypes.TEXT,
|
||
allowNull: true,
|
||
comment: '坐标点描述信息'
|
||
},
|
||
is_active: {
|
||
type: DataTypes.BOOLEAN,
|
||
allowNull: false,
|
||
defaultValue: true,
|
||
comment: '是否激活'
|
||
},
|
||
created_by: {
|
||
type: DataTypes.INTEGER,
|
||
allowNull: true,
|
||
comment: '创建人ID'
|
||
},
|
||
updated_by: {
|
||
type: DataTypes.INTEGER,
|
||
allowNull: true,
|
||
comment: '更新人ID'
|
||
}
|
||
};
|
||
|
||
// 模型选项
|
||
static options = {
|
||
tableName: 'electronic_fence_points',
|
||
comment: '电子围栏坐标点表',
|
||
indexes: [
|
||
{
|
||
fields: ['fence_id']
|
||
},
|
||
{
|
||
fields: ['fence_id', 'point_order']
|
||
},
|
||
{
|
||
fields: ['longitude', 'latitude']
|
||
},
|
||
{
|
||
fields: ['point_type']
|
||
},
|
||
{
|
||
fields: ['is_active']
|
||
}
|
||
],
|
||
hooks: {
|
||
beforeCreate: (point, options) => {
|
||
// 创建前钩子
|
||
if (!point.point_order && point.point_order !== 0) {
|
||
// 如果没有指定顺序,自动计算
|
||
return ElectronicFencePoint.count({
|
||
where: { fence_id: point.fence_id }
|
||
}).then(count => {
|
||
point.point_order = count;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
// 实例方法
|
||
/**
|
||
* 获取坐标点的经纬度对象
|
||
* @returns {Object} 包含lng和lat的对象
|
||
*/
|
||
getCoordinates() {
|
||
return {
|
||
lng: parseFloat(this.longitude),
|
||
lat: parseFloat(this.latitude)
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 设置坐标点
|
||
* @param {number} lng - 经度
|
||
* @param {number} lat - 纬度
|
||
*/
|
||
setCoordinates(lng, lat) {
|
||
this.longitude = lng;
|
||
this.latitude = lat;
|
||
}
|
||
|
||
/**
|
||
* 转换为前端格式
|
||
* @returns {Object} 前端使用的坐标点格式
|
||
*/
|
||
toFrontendFormat() {
|
||
return {
|
||
id: this.id,
|
||
fenceId: this.fence_id,
|
||
pointOrder: this.point_order,
|
||
lng: parseFloat(this.longitude),
|
||
lat: parseFloat(this.latitude),
|
||
pointType: this.point_type,
|
||
description: this.description,
|
||
isActive: this.is_active,
|
||
createdAt: this.created_at,
|
||
updatedAt: this.updated_at
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 计算到另一个点的距离(米)
|
||
* @param {ElectronicFencePoint} otherPoint - 另一个坐标点
|
||
* @returns {number} 距离(米)
|
||
*/
|
||
distanceTo(otherPoint) {
|
||
const R = 6371000; // 地球半径(米)
|
||
const lat1 = this.latitude * Math.PI / 180;
|
||
const lat2 = otherPoint.latitude * Math.PI / 180;
|
||
const deltaLat = (otherPoint.latitude - this.latitude) * Math.PI / 180;
|
||
const deltaLng = (otherPoint.longitude - this.longitude) * Math.PI / 180;
|
||
|
||
const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
|
||
Math.cos(lat1) * Math.cos(lat2) *
|
||
Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2);
|
||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||
|
||
return R * c;
|
||
}
|
||
|
||
/**
|
||
* 检查坐标点是否在指定范围内
|
||
* @param {number} centerLng - 中心点经度
|
||
* @param {number} centerLat - 中心点纬度
|
||
* @param {number} radius - 半径(米)
|
||
* @returns {boolean} 是否在范围内
|
||
*/
|
||
isWithinRadius(centerLng, centerLat, radius) {
|
||
const centerPoint = {
|
||
longitude: centerLng,
|
||
latitude: centerLat
|
||
};
|
||
return this.distanceTo(centerPoint) <= radius;
|
||
}
|
||
|
||
// 静态方法
|
||
/**
|
||
* 根据围栏ID获取所有坐标点
|
||
* @param {number} fenceId - 围栏ID
|
||
* @param {Object} options - 查询选项
|
||
* @returns {Promise<Array>} 坐标点数组
|
||
*/
|
||
static async getByFenceId(fenceId, options = {}) {
|
||
const defaultOptions = {
|
||
where: {
|
||
fence_id: fenceId,
|
||
is_active: true
|
||
},
|
||
order: [['point_order', 'ASC']]
|
||
};
|
||
|
||
return this.findAll({
|
||
...defaultOptions,
|
||
...options
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 批量创建坐标点
|
||
* @param {number} fenceId - 围栏ID
|
||
* @param {Array} points - 坐标点数组
|
||
* @param {Object} options - 创建选项
|
||
* @returns {Promise<Array>} 创建的坐标点数组
|
||
*/
|
||
static async createPoints(fenceId, points, options = {}) {
|
||
const pointsData = points.map((point, index) => ({
|
||
fence_id: fenceId,
|
||
point_order: index,
|
||
longitude: point.lng,
|
||
latitude: point.lat,
|
||
point_type: point.type || 'corner',
|
||
description: point.description || null,
|
||
created_by: options.createdBy || null
|
||
}));
|
||
|
||
return this.bulkCreate(pointsData, options);
|
||
}
|
||
|
||
/**
|
||
* 更新围栏的所有坐标点
|
||
* @param {number} fenceId - 围栏ID
|
||
* @param {Array} points - 新的坐标点数组
|
||
* @param {Object} options - 更新选项
|
||
* @returns {Promise<Array>} 更新后的坐标点数组
|
||
*/
|
||
static async updateFencePoints(fenceId, points, options = {}) {
|
||
const transaction = options.transaction || await sequelize.transaction();
|
||
|
||
try {
|
||
// 删除现有坐标点
|
||
await this.destroy({
|
||
where: { fence_id: fenceId },
|
||
transaction
|
||
});
|
||
|
||
// 创建新的坐标点
|
||
const newPoints = await this.createPoints(fenceId, points, {
|
||
...options,
|
||
transaction
|
||
});
|
||
|
||
if (!options.transaction) {
|
||
await transaction.commit();
|
||
}
|
||
|
||
return newPoints;
|
||
} catch (error) {
|
||
if (!options.transaction) {
|
||
await transaction.rollback();
|
||
}
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取围栏的边界框
|
||
* @param {number} fenceId - 围栏ID
|
||
* @returns {Promise<Object>} 边界框对象
|
||
*/
|
||
static async getFenceBounds(fenceId) {
|
||
const points = await this.getByFenceId(fenceId);
|
||
|
||
if (points.length === 0) {
|
||
return null;
|
||
}
|
||
|
||
const lngs = points.map(p => parseFloat(p.longitude));
|
||
const lats = points.map(p => parseFloat(p.latitude));
|
||
|
||
return {
|
||
minLng: Math.min(...lngs),
|
||
maxLng: Math.max(...lngs),
|
||
minLat: Math.min(...lats),
|
||
maxLat: Math.max(...lats),
|
||
center: {
|
||
lng: (Math.min(...lngs) + Math.max(...lngs)) / 2,
|
||
lat: (Math.min(...lats) + Math.max(...lats)) / 2
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
// 初始化模型
|
||
ElectronicFencePoint.init(ElectronicFencePoint.attributes, {
|
||
...ElectronicFencePoint.options,
|
||
sequelize,
|
||
modelName: 'ElectronicFencePoint'
|
||
});
|
||
|
||
module.exports = ElectronicFencePoint;
|