Files
nxxmdata/backend/models/ElectronicFencePoint.js

299 lines
7.7 KiB
JavaScript
Raw Normal View History

2025-09-12 20:08:42 +08:00
/**
* 电子围栏坐标点模型
* 用于存储围栏绘制过程中用户选定的经纬度坐标点
*/
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;