Files
nxxmdata/backend/models/ElectronicFencePoint.js
2025-09-12 20:08:42 +08:00

299 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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