/** * 电子围栏坐标点模型 * 用于存储围栏绘制过程中用户选定的经纬度坐标点 */ 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} 坐标点数组 */ 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} 创建的坐标点数组 */ 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} 更新后的坐标点数组 */ 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} 边界框对象 */ 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;