249 lines
6.1 KiB
JavaScript
249 lines
6.1 KiB
JavaScript
const { DataTypes } = require('sequelize')
|
|
const BaseModel = require('./BaseModel')
|
|
const { sequelize } = require('../config/database-simple')
|
|
|
|
/**
|
|
* 电子围栏模型
|
|
*/
|
|
class ElectronicFence extends BaseModel {
|
|
static init(sequelize) {
|
|
return super.init({
|
|
id: {
|
|
type: DataTypes.INTEGER,
|
|
primaryKey: true,
|
|
autoIncrement: true,
|
|
comment: '围栏ID'
|
|
},
|
|
name: {
|
|
type: DataTypes.STRING(100),
|
|
allowNull: false,
|
|
comment: '围栏名称'
|
|
},
|
|
type: {
|
|
type: DataTypes.ENUM('collector', 'grazing', 'safety'),
|
|
allowNull: false,
|
|
defaultValue: 'collector',
|
|
comment: '围栏类型: collector-采集器电子围栏, grazing-放牧围栏, safety-安全围栏'
|
|
},
|
|
description: {
|
|
type: DataTypes.TEXT,
|
|
allowNull: true,
|
|
comment: '围栏描述'
|
|
},
|
|
coordinates: {
|
|
type: DataTypes.JSON,
|
|
allowNull: false,
|
|
comment: '围栏坐标点数组'
|
|
},
|
|
center_lng: {
|
|
type: DataTypes.DECIMAL(10, 7),
|
|
allowNull: false,
|
|
comment: '围栏中心经度'
|
|
},
|
|
center_lat: {
|
|
type: DataTypes.DECIMAL(10, 7),
|
|
allowNull: false,
|
|
comment: '围栏中心纬度'
|
|
},
|
|
area: {
|
|
type: DataTypes.DECIMAL(10, 4),
|
|
allowNull: true,
|
|
comment: '围栏面积(平方米)'
|
|
},
|
|
grazing_status: {
|
|
type: DataTypes.ENUM('grazing', 'not_grazing'),
|
|
allowNull: false,
|
|
defaultValue: 'not_grazing',
|
|
comment: '放牧状态: grazing-放牧中, not_grazing-未放牧'
|
|
},
|
|
inside_count: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: false,
|
|
defaultValue: 0,
|
|
comment: '安全区域内动物数量'
|
|
},
|
|
outside_count: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: false,
|
|
defaultValue: 0,
|
|
comment: '安全区域外动物数量'
|
|
},
|
|
is_active: {
|
|
type: DataTypes.BOOLEAN,
|
|
allowNull: false,
|
|
defaultValue: true,
|
|
comment: '是否启用'
|
|
},
|
|
farm_id: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
comment: '关联农场ID'
|
|
},
|
|
created_by: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
comment: '创建人ID'
|
|
},
|
|
updated_by: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
comment: '更新人ID'
|
|
}
|
|
}, {
|
|
sequelize,
|
|
modelName: 'ElectronicFence',
|
|
tableName: 'electronic_fences',
|
|
timestamps: true,
|
|
createdAt: 'created_at',
|
|
updatedAt: 'updated_at',
|
|
comment: '电子围栏表',
|
|
indexes: [
|
|
{
|
|
name: 'idx_fence_name',
|
|
fields: ['name']
|
|
},
|
|
{
|
|
name: 'idx_fence_type',
|
|
fields: ['type']
|
|
},
|
|
{
|
|
name: 'idx_fence_center',
|
|
fields: ['center_lng', 'center_lat']
|
|
},
|
|
{
|
|
name: 'idx_fence_active',
|
|
fields: ['is_active']
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 定义关联关系(已在 models/index.js 中定义)
|
|
*/
|
|
// static associate(models) {
|
|
// // 围栏与农场关联(可选)
|
|
// this.belongsTo(models.Farm, {
|
|
// foreignKey: 'farm_id',
|
|
// as: 'farm'
|
|
// })
|
|
// }
|
|
|
|
/**
|
|
* 获取围栏类型文本
|
|
*/
|
|
getTypeText() {
|
|
const typeMap = {
|
|
'collector': '采集器电子围栏',
|
|
'grazing': '放牧围栏',
|
|
'safety': '安全围栏'
|
|
}
|
|
return typeMap[this.type] || '未知类型'
|
|
}
|
|
|
|
/**
|
|
* 获取放牧状态文本
|
|
*/
|
|
getGrazingStatusText() {
|
|
const statusMap = {
|
|
'grazing': '放牧中',
|
|
'not_grazing': '未放牧'
|
|
}
|
|
return statusMap[this.grazing_status] || '未知状态'
|
|
}
|
|
|
|
/**
|
|
* 计算围栏面积(简化计算)
|
|
*/
|
|
calculateArea() {
|
|
if (!this.coordinates || this.coordinates.length < 3) {
|
|
return 0
|
|
}
|
|
|
|
// 使用Shoelace公式计算多边形面积
|
|
let area = 0
|
|
const coords = this.coordinates
|
|
|
|
for (let i = 0; i < coords.length; i++) {
|
|
const j = (i + 1) % coords.length
|
|
area += coords[i].lng * coords[j].lat
|
|
area -= coords[j].lng * coords[i].lat
|
|
}
|
|
|
|
// 转换为平方米(粗略计算)
|
|
return Math.abs(area) * 111000 * 111000 / 2
|
|
}
|
|
|
|
/**
|
|
* 计算围栏中心点
|
|
*/
|
|
calculateCenter() {
|
|
if (!this.coordinates || this.coordinates.length === 0) {
|
|
return { lng: 0, lat: 0 }
|
|
}
|
|
|
|
let lngSum = 0
|
|
let latSum = 0
|
|
|
|
this.coordinates.forEach(coord => {
|
|
lngSum += coord.lng
|
|
latSum += coord.lat
|
|
})
|
|
|
|
return {
|
|
lng: lngSum / this.coordinates.length,
|
|
lat: latSum / this.coordinates.length
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查点是否在围栏内
|
|
*/
|
|
isPointInside(lng, lat) {
|
|
if (!this.coordinates || this.coordinates.length < 3) {
|
|
return false
|
|
}
|
|
|
|
let inside = false
|
|
const coords = this.coordinates
|
|
|
|
for (let i = 0, j = coords.length - 1; i < coords.length; j = i++) {
|
|
if (((coords[i].lat > lat) !== (coords[j].lat > lat)) &&
|
|
(lng < (coords[j].lng - coords[i].lng) * (lat - coords[i].lat) / (coords[j].lat - coords[i].lat) + coords[i].lng)) {
|
|
inside = !inside
|
|
}
|
|
}
|
|
|
|
return inside
|
|
}
|
|
|
|
/**
|
|
* 转换为前端格式
|
|
*/
|
|
toFrontendFormat() {
|
|
return {
|
|
id: this.id,
|
|
name: this.name,
|
|
type: this.getTypeText(),
|
|
description: this.description,
|
|
coordinates: this.coordinates,
|
|
center: {
|
|
lng: this.center_lng,
|
|
lat: this.center_lat
|
|
},
|
|
area: this.area,
|
|
grazingStatus: this.getGrazingStatusText(),
|
|
insideCount: this.inside_count,
|
|
outsideCount: this.outside_count,
|
|
isActive: this.is_active,
|
|
createdAt: this.created_at,
|
|
updatedAt: this.updated_at
|
|
}
|
|
}
|
|
}
|
|
|
|
// 初始化模型
|
|
ElectronicFence.init(sequelize)
|
|
|
|
module.exports = ElectronicFence
|