fajiao
2 years ago
87 changed files with 3257 additions and 2926 deletions
@ -1,12 +1,13 @@ |
|||
{ |
|||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", |
|||
"CameraDataOptions": { |
|||
"LazyInit": false, |
|||
"LoopInterval": "1000" |
|||
"CameraData": { |
|||
"CameraPtz": { |
|||
"LoopInterval": "1000", |
|||
"Timeout": "2000" |
|||
}, |
|||
"PTZServer": { |
|||
"Type": "", |
|||
"Ip": "127.0.0.1", |
|||
"Port": "7022" |
|||
"MarkSearcher": { |
|||
"LoopInterval": "1000", |
|||
"Timeout": "2000" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,44 @@ |
|||
namespace Cis.Application.Cb; |
|||
|
|||
/// <summary>
|
|||
/// 相机参数表
|
|||
/// </summary>
|
|||
[SugarTable(CbInfo.CbCameraParamsTbName, CbInfo.CbCameraParamsTbDesc)] |
|||
[Tenant(CbInfo.DbName)] |
|||
public class CbCameraParams : EntityBase |
|||
{ |
|||
/// <summary>
|
|||
/// 名称
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "名称", Length = 64)] |
|||
[Required, MaxLength(64)] |
|||
public string Name { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 相机类型
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "相机类型")] |
|||
[Required] |
|||
public int CameraType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// sdk 端口
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "sdk端口")] |
|||
[Required] |
|||
public int SdkPort { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// FocusX
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "FocusX")] |
|||
[Required] |
|||
public double FocusX { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// FocusY
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "FocusY")] |
|||
[Required] |
|||
public double FocusY { get; set; } |
|||
} |
@ -0,0 +1,61 @@ |
|||
using Newtonsoft.Json.Linq; |
|||
|
|||
namespace Cis.Application.Cb; |
|||
|
|||
/// <summary>
|
|||
/// 相机参数服务
|
|||
/// </summary>
|
|||
[ApiDescriptionSettings(CbInfo.GroupName, Order = CbInfo.GroupOrder)] |
|||
public class CbCameraParamsService : IDynamicApiController, ITransient |
|||
{ |
|||
private readonly SqlSugarRepository<CbCameraParams> _baseRep; |
|||
|
|||
public CbCameraParamsService(SqlSugarRepository<CbCameraParams> baseRep) |
|||
{ |
|||
_baseRep = baseRep; |
|||
} |
|||
|
|||
[HttpPost] |
|||
public async Task<bool> Add([FromForm] CbCameraParams entity) |
|||
{ |
|||
return await _baseRep.InsertAsync(entity); |
|||
} |
|||
|
|||
[HttpPost] |
|||
public async Task<bool> Update([FromForm] CbCameraParams entity) |
|||
{ |
|||
return await _baseRep.UpdateAsync(entity); |
|||
} |
|||
|
|||
[HttpPost] |
|||
public async Task<bool> Delete([FromForm] CbCameraParams entity) |
|||
{ |
|||
return await _baseRep.DeleteAsync(entity); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public async Task<CbCameraParams> Get(long id) |
|||
{ |
|||
CbCameraParams entity = await _baseRep.GetByIdAsync(id); |
|||
return entity; |
|||
} |
|||
|
|||
[HttpGet] |
|||
public async Task<List<CbCameraParams>> GetList(string queryJson = "") |
|||
{ |
|||
JObject queryObj = queryJson.ToJObject(); |
|||
List<CbCameraParams> list = await _baseRep.AsQueryable() |
|||
.ToListAsync(); |
|||
return list; |
|||
} |
|||
|
|||
[HttpGet] |
|||
public async Task<List<CbCameraParams>> GetPageList(string queryJson, string pagination) |
|||
{ |
|||
Pagination pageObj = pagination.ToObject<Pagination>(); |
|||
JObject queryObj = queryJson.ToJObject(); |
|||
List<CbCameraParams> list = await _baseRep.AsQueryable() |
|||
.ToPageListAsync(pageObj.Index, pageObj.Size); |
|||
return list; |
|||
} |
|||
} |
@ -1,80 +0,0 @@ |
|||
using System.Drawing; |
|||
|
|||
namespace Cis.Application.Core; |
|||
|
|||
public class HikMarkSeacher : MarkSearcherBase |
|||
{ |
|||
public HikMarkSeacher(CameraCalcInfo cameraCalcInfo) : base(cameraCalcInfo) |
|||
{ |
|||
} |
|||
|
|||
#region Implement
|
|||
|
|||
protected override double ConvertPanPosToAngle(double panPos) |
|||
{ |
|||
double ret = 0.1 * HexToDecMa(panPos) / 180 * Math.PI; |
|||
ret = (ret >= 0) ? ret : (2 * Math.PI + ret); |
|||
return ret; |
|||
} |
|||
|
|||
protected override double ConvertTiltPosToAngle(double tiltPos, double tiltMinPos = 0) |
|||
{ |
|||
double ndiff; |
|||
if (tiltPos > tiltMinPos) |
|||
ndiff = HexToDecMa(tiltPos) - HexToDecMa(tiltMinPos); |
|||
else |
|||
ndiff = HexToDecMa(tiltPos) + HexToDecMa(13824) - HexToDecMa(tiltMinPos); |
|||
double ret = 0.1 * ndiff / 180 * Math.PI; |
|||
return ret; |
|||
} |
|||
|
|||
protected override PointF GetFOfMatrixByZoomPos(double zoomPos) |
|||
{ |
|||
PointF pointF = new() |
|||
{ |
|||
X = (float)GetFx(zoomPos), |
|||
Y = (float)GetFy(zoomPos) |
|||
}; |
|||
return pointF; |
|||
} |
|||
|
|||
protected override void CalcSensor() |
|||
{ |
|||
_cameraCalcInfo.MinFocusX = 1783.6 / _cameraCalcInfo.VideoWidth; |
|||
_cameraCalcInfo.MinFocusY = 1781.4 / _cameraCalcInfo.VideoHeight; |
|||
} |
|||
|
|||
#endregion Implement
|
|||
|
|||
#region Util
|
|||
|
|||
protected virtual double GetFx(double zoomPos) |
|||
{ |
|||
CameraCalcInfo calcInfo = _cameraCalcInfo; |
|||
return calcInfo.MinFocusX * GetZoomTag(zoomPos); |
|||
} |
|||
|
|||
protected virtual double GetFy(double zoomPos) |
|||
{ |
|||
CameraCalcInfo calcInfo = _cameraCalcInfo; |
|||
return calcInfo.MinFocusY * GetZoomTag(zoomPos); |
|||
} |
|||
|
|||
protected virtual double GetZoomTag(double zoomPos) |
|||
{ |
|||
double ret = HexToDecMa(zoomPos) * 0.1; |
|||
ret = (ret - 1) * 0.65 + 1; |
|||
return ret; |
|||
} |
|||
|
|||
protected int HexToDecMa(double wHex) |
|||
{ |
|||
int wHexInt = wHex.ToInt(); |
|||
int ret = (wHexInt / 4096) * 1000 + ((wHexInt % 4096) / 256) * 100 + ((wHexInt % 256) / 16) * 10 + (wHexInt % 16); |
|||
return ret; |
|||
} |
|||
|
|||
|
|||
|
|||
#endregion Util
|
|||
} |
@ -1,260 +0,0 @@ |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.LinearAlgebra.Double; |
|||
using System.Collections.Concurrent; |
|||
using System.Drawing; |
|||
|
|||
namespace Cis.Application.Core; |
|||
|
|||
public abstract class MarkSearcherBase |
|||
{ |
|||
#region Attr
|
|||
|
|||
/// <summary>
|
|||
/// 当前相机参数信息
|
|||
/// </summary>
|
|||
protected CameraCalcInfo _cameraCalcInfo { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// _markLabelInfoList 锁对象,写锁
|
|||
/// </summary>
|
|||
private static object mliListLock { get; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 需要计算的 markLabelInfo 列表
|
|||
/// </summary>
|
|||
protected ConcurrentDictionary<long, MarkLabelCalcInfo> _markLabelInfoDict { get; set; } = new(); |
|||
|
|||
//protected MatrixBuilder<double> MBuilder { get; set; } = Matrix<double>.Build;
|
|||
|
|||
/// <summary>
|
|||
/// 世界坐标转化为相机坐标矩阵
|
|||
/// </summary>
|
|||
protected Matrix<double> World2CameraMatrix { get; set; } |
|||
|
|||
#endregion Attr
|
|||
|
|||
public MarkSearcherBase(CameraCalcInfo cameraCalcInfo) |
|||
{ |
|||
UpdateCameraCalcInfoRelated(cameraCalcInfo); |
|||
} |
|||
|
|||
#region Calc
|
|||
|
|||
/// <summary>
|
|||
/// 计算标签位置过程
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public List<MarkLabelCalcResult> Calc() |
|||
{ |
|||
List<MarkLabelCalcResult> resultList = new(); |
|||
|
|||
if (World2CameraMatrix == null || _markLabelInfoDict.IsEmpty) |
|||
return resultList; |
|||
|
|||
foreach (MarkLabelCalcInfo item in _markLabelInfoDict.Values) |
|||
{ |
|||
Matrix<double> labelC2WMatrix = ConvertCameraToWorld(item); |
|||
Matrix<double> labelPointMatrix = new DenseMatrix(3, 1, new double[] |
|||
{ |
|||
(item.CanvasLeftRatio * item.VideoWidth) / _cameraCalcInfo.VideoWidth - 0.5 , |
|||
(item.CanvasTopRatio * item.VideoHeight) / _cameraCalcInfo.VideoHeight - 0.5 , |
|||
1 |
|||
}); |
|||
Matrix<double> lResult = labelC2WMatrix.Multiply(labelPointMatrix); |
|||
Matrix<double> pResult = World2CameraMatrix.Multiply(lResult); |
|||
|
|||
double x = pResult[0, 0] / pResult[2, 0] + 0.5; |
|||
double y = pResult[1, 0] / pResult[2, 0] + 0.5; |
|||
MarkLabelCalcResult labelCalcResult; |
|||
if (x > 0.99 || x < 0.01 || y > 0.99 || y < 0.01 || pResult[2, 0] < 0) |
|||
labelCalcResult = MarkLabelCalcResult.New(item.Id, false); |
|||
else |
|||
labelCalcResult = MarkLabelCalcResult.New(item.Id, true, x, y); |
|||
resultList.Add(labelCalcResult); |
|||
} |
|||
return resultList; |
|||
} |
|||
|
|||
public async Task<List<MarkLabelCalcResult>> CalcAsync() |
|||
{ |
|||
return await Task.Run(Calc); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 判断相机是否进行了转动,转动了则需要重新计算世界坐标到相机坐标的转换矩阵
|
|||
/// </summary>
|
|||
/// <param name="newInfo"></param>
|
|||
/// <returns></returns>
|
|||
protected bool IsCameraRotate(PtzInfo newInfo) |
|||
{ |
|||
bool ret = _cameraCalcInfo.PtzInfo.Pan != newInfo.Pan || |
|||
_cameraCalcInfo.PtzInfo.Tilt != newInfo.Tilt || |
|||
_cameraCalcInfo.PtzInfo.Zoom != newInfo.Zoom; |
|||
return ret; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取将世界坐标系中的点转化为相机坐标系中的点的转换矩阵
|
|||
/// </summary>
|
|||
/// <param name="cameraCalcInfo"></param>
|
|||
/// <returns></returns>
|
|||
protected Matrix<double> ConvertWorldToCamera(CameraCalcInfo cameraCalcInfo) |
|||
{ |
|||
double panAngle = ConvertPanPosToAngle(cameraCalcInfo.PtzInfo.Pan); |
|||
double tiltAngle = ConvertTiltPosToAngle(cameraCalcInfo.PtzInfo.Tilt); |
|||
PointF pointF = GetFOfMatrixByZoomPos(cameraCalcInfo.PtzInfo.Zoom); |
|||
double sinPan = Math.Sin(panAngle); |
|||
double cosPan = Math.Cos(panAngle); |
|||
double sinTilt = Math.Sin(tiltAngle); |
|||
double cosTilt = Math.Cos(tiltAngle); |
|||
|
|||
Matrix<double> fMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
pointF.X, 0, 0 , |
|||
0, pointF.Y, 0 , |
|||
0, 0, 1 , |
|||
}); |
|||
|
|||
Matrix<double> rotateTiltMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
1, 0, 0 , |
|||
0, cosTilt, sinTilt , |
|||
0, -sinTilt, cosTilt , |
|||
}); |
|||
|
|||
Matrix<double> midResult = fMatrix.Multiply(rotateTiltMatrix); |
|||
|
|||
Matrix<double> rotatePanMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
cosPan, 0, sinPan , |
|||
0, 1, 0 , |
|||
-sinPan, 0, cosPan , |
|||
}); |
|||
|
|||
var last = midResult.Multiply(rotatePanMatrix); |
|||
return last; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取将相机坐标系中的点转化为世界坐标系中的点的转换矩阵
|
|||
/// </summary>
|
|||
/// <param name="labelCalcInfo"></param>
|
|||
/// <returns></returns>
|
|||
protected Matrix<double> ConvertCameraToWorld(MarkLabelCalcInfo labelCalcInfo) |
|||
{ |
|||
double panAngle = ConvertPanPosToAngle(labelCalcInfo.PtzInfo.Pan); |
|||
double tiltAngle = ConvertTiltPosToAngle(labelCalcInfo.PtzInfo.Tilt); |
|||
PointF pointF = GetFOfMatrixByZoomPos(labelCalcInfo.PtzInfo.Zoom); |
|||
double sinPan = Math.Sin(panAngle); |
|||
double cosPan = Math.Cos(panAngle); |
|||
double sinTilt = Math.Sin(tiltAngle); |
|||
double cosTilt = Math.Cos(tiltAngle); |
|||
|
|||
Matrix<double> rotatePanMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
cosPan, 0, -sinPan , |
|||
0, 1, 0 , |
|||
sinPan, 0, cosPan |
|||
}); |
|||
|
|||
Matrix<double> rotateTiltMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
1, 0, 0 , |
|||
0, cosTilt, -sinTilt , |
|||
0, sinTilt, cosTilt |
|||
}); |
|||
|
|||
Matrix<double> midResult = rotatePanMatrix.Multiply(rotateTiltMatrix); |
|||
|
|||
Matrix<double> fMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
(1 / pointF.X), 0, 0 , |
|||
0, (1 / pointF.Y), 0 , |
|||
0, 0, 1 |
|||
}); |
|||
|
|||
var last = midResult.Multiply(fMatrix); |
|||
return last; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 此方法计算在球机zoom值最小的情况下成像矩阵中的 f 本质为获取像元大小
|
|||
/// 尝试方案1:通过计算的方式来获取
|
|||
/// 尝试方案2:通过张友定相机标定的方法来生成成像矩阵中的 f
|
|||
/// </summary>
|
|||
protected virtual void CalcSensor() |
|||
{ |
|||
} |
|||
|
|||
#endregion Calc
|
|||
|
|||
#region Util
|
|||
|
|||
/// <summary>
|
|||
/// 将Pan值转化为角度
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
protected abstract double ConvertPanPosToAngle(double panPos); |
|||
|
|||
/// <summary>
|
|||
/// 将Tilt转化为角度
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
protected abstract double ConvertTiltPosToAngle(double tiltPos, double tiltMinPos = 0); |
|||
|
|||
/// <summary>
|
|||
/// 根据当前zoom值获取相机矩阵参数
|
|||
/// </summary>
|
|||
/// <param name="zoomPos"></param>
|
|||
/// <returns></returns>
|
|||
protected abstract PointF GetFOfMatrixByZoomPos(double zoomPos); |
|||
|
|||
#endregion Util
|
|||
|
|||
#region Operate Attr
|
|||
|
|||
protected void UpdateCameraCalcInfoRelated(CameraCalcInfo cameraCalcInfo) |
|||
{ |
|||
_cameraCalcInfo = cameraCalcInfo; |
|||
CalcSensor(); |
|||
World2CameraMatrix = ConvertWorldToCamera(cameraCalcInfo); |
|||
} |
|||
|
|||
public void UpdateCameraCalcInfo(PtzInfo ptzInfo) |
|||
{ |
|||
if (IsCameraRotate(ptzInfo)) |
|||
{ |
|||
_cameraCalcInfo.PtzInfo = ptzInfo; |
|||
World2CameraMatrix = ConvertWorldToCamera(_cameraCalcInfo); |
|||
} |
|||
} |
|||
|
|||
public bool AddMarkLabelCalcInfo(MarkLabelCalcInfo info) |
|||
{ |
|||
lock (mliListLock) |
|||
{ |
|||
if (_markLabelInfoDict.ContainsKey(info.Id)) |
|||
return false; |
|||
return _markLabelInfoDict.TryAdd(info.Id, info); |
|||
} |
|||
} |
|||
|
|||
public bool DeleteMarkLabelCalcInfo(long id) |
|||
{ |
|||
lock (mliListLock) |
|||
{ |
|||
if (!_markLabelInfoDict.ContainsKey(id)) |
|||
return true; |
|||
return _markLabelInfoDict.Remove(id); |
|||
} |
|||
} |
|||
|
|||
public bool ExistsMarkLabelCalcInfo(long id) |
|||
{ |
|||
if (_markLabelInfoDict.ContainsKey(id)) |
|||
return true; |
|||
return false; |
|||
} |
|||
|
|||
#endregion Operate Attr
|
|||
} |
@ -1,9 +0,0 @@ |
|||
namespace Cis.Application.Core; |
|||
|
|||
/// <summary>
|
|||
/// Ptz Api 接口
|
|||
/// </summary>
|
|||
public interface IPtzApi |
|||
{ |
|||
|
|||
} |
@ -1,205 +0,0 @@ |
|||
using System.Net.Sockets; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Cis.Application.Core; |
|||
|
|||
/// <summary>
|
|||
/// Ptz Api
|
|||
/// </summary>
|
|||
public class PtzServerApi : IPtzApi, ISingleton |
|||
{ |
|||
#region Attr
|
|||
|
|||
private TcpClient _tcpClient { get; set; } |
|||
|
|||
private NetworkStream _stream { get; set; } |
|||
|
|||
#endregion Attr
|
|||
|
|||
public PtzServerApi() |
|||
{ |
|||
PtzServerOptions options = App.GetOptions<PtzServerOptions>(); |
|||
_tcpClient = new TcpClient(options.Ip, options.Port); |
|||
//创建一个 networkstream 用来写入和读取数据
|
|||
_stream = _tcpClient.GetStream(); |
|||
} |
|||
|
|||
public RequestRealControl GetPtzRrc(int cameraId) |
|||
{ |
|||
RequestRealControl realControl = new(); |
|||
try |
|||
{ |
|||
string recieve_string = string.Empty; |
|||
realControl.token = 666; |
|||
|
|||
realControl.CameraInfo.cameraid = cameraId; |
|||
realControl.Status = true; |
|||
realControl.realControlType = RealControlType.PTZINFO_GET_; |
|||
byte[] data = StructToByte(realControl); |
|||
_stream.Write(data, 0, data.Length); |
|||
|
|||
byte[] recieve_byte = new byte[_tcpClient.ReceiveBufferSize]; |
|||
_stream.Read(recieve_byte, 0, (int)_tcpClient.ReceiveBufferSize); |
|||
//stream.BeginRead(recieve_byte, 0, (int)tcp_client.ReceiveBufferSize,EndRead, stream);
|
|||
realControl = (RequestRealControl)BytetoStruct(recieve_byte, typeof(RequestRealControl)); |
|||
if (realControl.PTZPositionInfo.FT < 0) |
|||
{ |
|||
realControl.PTZPositionInfo.FT = 3600 + realControl.PTZPositionInfo.FT; |
|||
} |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
realControl = default; |
|||
} |
|||
return realControl; |
|||
} |
|||
|
|||
public PtzInfo GetPtzInfo(int cameraId) |
|||
{ |
|||
RequestRealControl rrc = GetPtzRrc(cameraId); |
|||
PtzInfo ptzInfo = PtzInfo.New(rrc.PTZPositionInfo.FP, rrc.PTZPositionInfo.FT, rrc.PTZPositionInfo.FZ); |
|||
return ptzInfo; |
|||
} |
|||
|
|||
public static byte[] StructToByte(object structObj) |
|||
{ |
|||
//获取结构体大小
|
|||
int size = Marshal.SizeOf(structObj); |
|||
|
|||
byte[] data = new byte[size]; |
|||
|
|||
//分配内存空间
|
|||
IntPtr structPtr = Marshal.AllocHGlobal(size); |
|||
// 将结构体数据复制到内存空间
|
|||
Marshal.StructureToPtr(structObj, structPtr, false); |
|||
// 将内存空间的数据拷贝到byte数组
|
|||
Marshal.Copy(structPtr, data, 0, size); |
|||
//释放内存
|
|||
Marshal.FreeHGlobal(structPtr); |
|||
return data; |
|||
} |
|||
|
|||
public static object BytetoStruct(byte[] bytes, Type type) |
|||
{ |
|||
object obj = new object(); |
|||
try |
|||
{ |
|||
byte[] temp = bytes; |
|||
// 获取结构体大小
|
|||
int size = Marshal.SizeOf(type); |
|||
if (size > bytes.Length) |
|||
return null; |
|||
// 分配结构体内存空间
|
|||
IntPtr structPtr = Marshal.AllocHGlobal(size); |
|||
// 将byte数组内容拷贝到内存中
|
|||
Marshal.Copy(temp, 0, structPtr, size); |
|||
|
|||
// 将内存空间转化为目标结构体
|
|||
obj = Marshal.PtrToStructure(structPtr, type); |
|||
//释放内存
|
|||
Marshal.FreeHGlobal(structPtr); |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
} |
|||
return obj; |
|||
} |
|||
|
|||
} |
|||
|
|||
#region 与ptz服务交互使用结构体
|
|||
|
|||
//----------------------------------------------------------------------------------------
|
|||
// 与ptz服务交互使用结构体
|
|||
//----------------------------------------------------------------------------------------
|
|||
//注意这个属性不能少
|
|||
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] |
|||
public struct RequestRealControl |
|||
{ |
|||
public int token; |
|||
|
|||
public CameraInfo CameraInfo; |
|||
|
|||
public RealControlType realControlType; |
|||
|
|||
//请求时 状态 true:开始;false:结束 答复时:true:成功;其他:失败
|
|||
public bool Status; |
|||
|
|||
public PresentInfo PresentInfo; |
|||
|
|||
public PTZPosInfo PTZPositionInfo; |
|||
//int数组,SizeConst表示数组的个数,在转换成
|
|||
//byte数组前必须先初始化数组,再使用,初始化
|
|||
//的数组长度必须和SizeConst一致,例test = new int[6];
|
|||
//[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
|||
//public int[] test;
|
|||
} |
|||
|
|||
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] |
|||
public struct CameraInfo |
|||
{ |
|||
public int cameraid; |
|||
|
|||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)] |
|||
public char[] ip; |
|||
|
|||
public ushort port; |
|||
|
|||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)] |
|||
private char[] user; |
|||
|
|||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)] |
|||
private char[] password; |
|||
} |
|||
|
|||
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] |
|||
public struct PresentInfo |
|||
{ |
|||
public int presentNo;//预置位编号
|
|||
|
|||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] |
|||
private char[] PresentName; // 预置位名称
|
|||
} |
|||
|
|||
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] |
|||
public struct PTZPosInfo |
|||
{ |
|||
public int FP;//p信息
|
|||
public int FT;//T信息
|
|||
public int FZ;//Z信息
|
|||
public int TitlePosMin;//描述点垂直参数min
|
|||
public int ZoomPosMin;//描述点变倍参数min
|
|||
|
|||
public float FHorWidth;//水平宽度 精确到小数点后两位 *10000
|
|||
public float FVerWidth;//垂直宽度 精确到小数点后两位 *10000
|
|||
public float FFold;//zoom=1没变时的焦距 精确到小数点后两位 *100
|
|||
|
|||
public int CameraType; |
|||
} |
|||
|
|||
public enum RealControlType |
|||
{ |
|||
MOVE_UP_ = 1, |
|||
MOVE_DOWN_, |
|||
MOVE_LEFT_, |
|||
MOVE_RIGHT_, |
|||
MOVE_LEFTUP_, |
|||
MOVE_LEFTDOWN_, |
|||
MOVE_RIGHTUP_, |
|||
MOVE_RIGHTDOWN_, |
|||
|
|||
ZOOM_IN_, |
|||
ZOOM_OUT_, |
|||
FOCUS_NEAR_, |
|||
FOCUS_FAR_, |
|||
IRIS_OPEN_, |
|||
IRIS_CLOSE_, |
|||
|
|||
PRESET_SET_, |
|||
PRESET_CALL_, |
|||
|
|||
PTZINFO_GET_, |
|||
PTZINFO_SET_ |
|||
} |
|||
|
|||
#endregion 与ptz服务交互使用结构体
|
@ -0,0 +1,23 @@ |
|||
namespace Cis.Application.Core; |
|||
|
|||
public class CacheInfo |
|||
{ |
|||
#region Cache Name
|
|||
|
|||
/// <summary>
|
|||
/// CameraId2Ip 字典
|
|||
/// </summary>
|
|||
public static string CameraId2Ip { get; set; } = "CameraId2Ip"; |
|||
|
|||
/// <summary>
|
|||
/// CameraIpCounts 字典
|
|||
/// </summary>
|
|||
public static string CameraIpCounts { get; set; } = "CameraIpCounts"; |
|||
|
|||
/// <summary>
|
|||
/// CameraPtzInfos 字典
|
|||
/// </summary>
|
|||
public static string CameraPtzInfos { get; set; } = "CameraPtzInfos"; |
|||
|
|||
#endregion Cache Name
|
|||
} |
@ -0,0 +1,18 @@ |
|||
namespace Cis.Application.Core; |
|||
|
|||
public class CoreInfo |
|||
{ |
|||
#region Api Info
|
|||
|
|||
/// <summary>
|
|||
/// Api 分组名
|
|||
/// </summary>
|
|||
public const string GroupName = "Core"; |
|||
|
|||
/// <summary>
|
|||
/// Api 分组排序
|
|||
/// </summary>
|
|||
public const int GroupOrder = 100; |
|||
|
|||
#endregion Api Info
|
|||
} |
@ -0,0 +1,30 @@ |
|||
using Cis.Application.Core.Component.MarkSeacher; |
|||
using EC.Helper.CameraSDK; |
|||
|
|||
namespace Cis.Application.Core; |
|||
|
|||
[SuppressSniffer] |
|||
public static class Extension |
|||
{ |
|||
public static ICameraSDK CreateCameraSDK(this CameraType cameraType, CameraInfo cameraInfo) |
|||
{ |
|||
return cameraType switch |
|||
{ |
|||
CameraType.HiK => new HiKSDK(cameraInfo), |
|||
CameraType.DaHua => new DaHuaSDK(cameraInfo), |
|||
CameraType.YuShi => new YuShiSDK(cameraInfo), |
|||
_ => null, |
|||
}; |
|||
} |
|||
|
|||
public static MarkSearcherBase CreateMarkSearcher(this CameraType cameraType, CameraCalcParams cameraCalcParams) |
|||
{ |
|||
return cameraType switch |
|||
{ |
|||
CameraType.HiK => new HiKMarkSearcher(cameraCalcParams), |
|||
CameraType.DaHua => new DaHuaMarkSearcher(cameraCalcParams), |
|||
CameraType.YuShi => new YuShiMarkSearcher(cameraCalcParams), |
|||
_ => null |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
using Cis.Application.Core.Component.PtzServer; |
|||
using EC.Helper.CameraSDK; |
|||
using StackExchange.Redis; |
|||
using System.Collections.Concurrent; |
|||
|
|||
namespace Cis.Application.Core.Component.CameraSDK; |
|||
|
|||
/// <summary>
|
|||
/// 相机 sdk 服务类
|
|||
/// </summary>
|
|||
public class CameraSdkServer : ICameraSdkServer, ISingleton |
|||
{ |
|||
#region Attr
|
|||
|
|||
private readonly IDatabase _cache; |
|||
|
|||
/// <summary>
|
|||
/// {ip, ICameraSDK}
|
|||
/// </summary>
|
|||
private ConcurrentDictionary<string, ICameraSDK> CameraSdkDict { get; set; } = new(); |
|||
|
|||
#endregion Attr
|
|||
|
|||
public CameraSdkServer(IDatabase cache) |
|||
{ |
|||
_cache = cache; |
|||
} |
|||
|
|||
#region Base Method
|
|||
|
|||
public bool RegisterCamera(CameraInfo cameraInfo) |
|||
{ |
|||
bool ret = CameraSdkDict.ContainsKey(cameraInfo.Ip); |
|||
if (ret) return false; |
|||
ICameraSDK cameraSDK = ((CameraType)cameraInfo.Type).CreateCameraSDK(cameraInfo); |
|||
if (cameraSDK == null) return false; |
|||
ret = cameraSDK.Init(); |
|||
if (!ret) return false; |
|||
ret = CameraSdkDict.TryAdd(cameraInfo.Ip, cameraSDK); |
|||
return ret; |
|||
} |
|||
|
|||
public bool DeleteCamera(string ip) |
|||
{ |
|||
return CameraSdkDict.TryRemove(ip, out _); |
|||
} |
|||
|
|||
public bool IsExistsCamera(string ip) |
|||
{ |
|||
return CameraSdkDict.ContainsKey(ip); |
|||
} |
|||
|
|||
#endregion Base Method
|
|||
|
|||
#region Main Method
|
|||
|
|||
public bool GetPtzInfoById(string cameraId, out PtzInfo ptzInfo) |
|||
{ |
|||
RedisValue value = _cache.HashGet(CacheInfo.CameraId2Ip, cameraId); |
|||
if (value.IsNull) { ptzInfo = PtzInfo.Default; return false; } |
|||
string ip = value.ToString(); |
|||
return GetPtzInfoByIp(ip, out ptzInfo); |
|||
} |
|||
|
|||
public bool GetPtzInfoByIp(string ip, out PtzInfo ptzInfo) |
|||
{ |
|||
bool ret = CameraSdkDict.TryGetValue(ip, out ICameraSDK cameraSDK); |
|||
if (!ret) { ptzInfo = PtzInfo.Default; return false; } |
|||
return cameraSDK.GetPtzInfo(out ptzInfo); |
|||
} |
|||
|
|||
#endregion Main Method
|
|||
} |
@ -0,0 +1,54 @@ |
|||
using EC.Helper.CameraSDK; |
|||
|
|||
namespace Cis.Application.Core.Component.PtzServer; |
|||
|
|||
/// <summary>
|
|||
/// 相机 sdk 服务接口
|
|||
/// </summary>
|
|||
public interface ICameraSdkServer |
|||
{ |
|||
#region Base Method
|
|||
|
|||
/// <summary>
|
|||
/// 注册相机
|
|||
/// </summary>
|
|||
/// <param name="cameraInfo"></param>
|
|||
/// <returns></returns>
|
|||
public bool RegisterCamera(CameraInfo cameraInfo); |
|||
|
|||
/// <summary>
|
|||
/// 注销相机
|
|||
/// </summary>
|
|||
/// <param name="ip"></param>
|
|||
/// <returns></returns>
|
|||
public bool DeleteCamera(string ip); |
|||
|
|||
/// <summary>
|
|||
/// 是否存在相机
|
|||
/// </summary>
|
|||
/// <param name="ip"></param>
|
|||
/// <returns></returns>
|
|||
public bool IsExistsCamera(string ip); |
|||
|
|||
#endregion Base Method
|
|||
|
|||
#region Main Method
|
|||
|
|||
/// <summary>
|
|||
/// 获取 ptz
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <param name="ptzInfo"></param>
|
|||
/// <returns></returns>
|
|||
public bool GetPtzInfoById(string cameraId, out PtzInfo ptzInfo); |
|||
|
|||
/// <summary>
|
|||
/// 获取 ptz
|
|||
/// </summary>
|
|||
/// <param name="ip"></param>
|
|||
/// <param name="ptzInfo"></param>
|
|||
/// <returns></returns>
|
|||
public bool GetPtzInfoByIp(string ip, out PtzInfo ptzInfo); |
|||
|
|||
#endregion Main Method
|
|||
} |
@ -0,0 +1,52 @@ |
|||
using EC.Helper.CameraSDK; |
|||
|
|||
namespace Cis.Application.Core.Component.MarkSeacher; |
|||
|
|||
/// <summary>
|
|||
/// 相机计算参数
|
|||
/// </summary>
|
|||
public class CameraCalcParams |
|||
{ |
|||
/// <summary>
|
|||
/// Camera Id
|
|||
/// </summary>
|
|||
public long Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Ptz 信息
|
|||
/// </summary>
|
|||
public PtzInfo PtzInfo { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// FocusX
|
|||
/// </summary>
|
|||
public double FocusX { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// FocusY
|
|||
/// </summary>
|
|||
public double FocusY { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 视频宽度
|
|||
/// </summary>
|
|||
public int VideoWidth { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 视频高度
|
|||
/// </summary>
|
|||
public int VideoHeight { get; set; } |
|||
|
|||
public static CameraCalcParams New(long id, PtzInfo ptzInfo, double focusX, double focusY, int videoWidth = 1920, int videoHeight = 1080) |
|||
{ |
|||
return new() |
|||
{ |
|||
Id = id, |
|||
PtzInfo = ptzInfo, |
|||
FocusX = focusX, |
|||
FocusY = focusY, |
|||
VideoWidth = videoWidth, |
|||
VideoHeight = videoHeight, |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,52 @@ |
|||
using EC.Helper.CameraSDK; |
|||
|
|||
namespace Cis.Application.Core.Component.MarkSeacher; |
|||
|
|||
/// <summary>
|
|||
/// 标签计算参数
|
|||
/// </summary>
|
|||
public class MarkLabelCalcParams |
|||
{ |
|||
/// <summary>
|
|||
/// MarkLabel Id
|
|||
/// </summary>
|
|||
public long Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Ptz 信息
|
|||
/// </summary>
|
|||
public PtzInfo PtzInfo { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 视频宽度
|
|||
/// </summary>
|
|||
public double VideoWidth { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 视频高度
|
|||
/// </summary>
|
|||
public double VideoHeight { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 画布 left 距离比例
|
|||
/// </summary>
|
|||
public double CanvasLeftRatio { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 画布 top 距离比例
|
|||
/// </summary>
|
|||
public double CanvasTopRatio { get; set; } |
|||
|
|||
public static MarkLabelCalcParams New(long id, PtzInfo ptzInfo, double videoWidth, double videoHeight, double canvasLeftRatio, double canvasTopRatio) |
|||
{ |
|||
return new() |
|||
{ |
|||
Id = id, |
|||
PtzInfo = ptzInfo, |
|||
VideoWidth = videoWidth, |
|||
VideoHeight = videoHeight, |
|||
CanvasLeftRatio = canvasLeftRatio, |
|||
CanvasTopRatio = canvasTopRatio |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
namespace Cis.Application.Core.Component.MarkSeacher; |
|||
|
|||
/// <summary>
|
|||
/// 标签计算结果
|
|||
/// </summary>
|
|||
public class MarkLabelCalcResult |
|||
{ |
|||
/// <summary>
|
|||
/// MarkLabel Id
|
|||
/// </summary>
|
|||
public long Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// true 显示(在当前视频画面里面)
|
|||
/// false 不显示(不在当前视频画面里面)
|
|||
/// </summary>
|
|||
public bool InFlag { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 画布 left 距离比例
|
|||
/// </summary>
|
|||
public double CanvasLeftRatio { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 画布 top 距离比例
|
|||
/// </summary>
|
|||
public double CanvasTopRatio { get; set; } |
|||
|
|||
public static MarkLabelCalcResult New(long id, bool inFlag) |
|||
{ |
|||
return new() |
|||
{ |
|||
Id = id, |
|||
InFlag = inFlag |
|||
}; |
|||
} |
|||
|
|||
public static MarkLabelCalcResult New(long id, bool inFlag, double canvasLeftRatio, double canvasTopRatio) |
|||
{ |
|||
return new() |
|||
{ |
|||
Id = id, |
|||
InFlag = inFlag, |
|||
CanvasLeftRatio = canvasLeftRatio, |
|||
CanvasTopRatio = canvasTopRatio |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,119 @@ |
|||
namespace Cis.Application.Core.Component.MarkSeacher; |
|||
|
|||
/// <summary>
|
|||
/// 追踪标签服务接口
|
|||
/// </summary>
|
|||
public interface IMarkSearcherServer |
|||
{ |
|||
#region Base Method
|
|||
|
|||
/// <summary>
|
|||
/// 激活相机进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <returns></returns>
|
|||
public bool ActivateSearcher(long cameraId); |
|||
|
|||
/// <summary>
|
|||
/// 解除相机进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <returns></returns>
|
|||
public bool DeactivateSearcher(long cameraId); |
|||
|
|||
/// <summary>
|
|||
/// 是否存在相机进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <returns></returns>
|
|||
public bool IsExistsSearcher(long cameraId); |
|||
|
|||
/// <summary>
|
|||
/// 获取 Searcher
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <returns></returns>
|
|||
public MarkSearcherBase GetSearcher(long cameraId); |
|||
|
|||
/// <summary>
|
|||
/// 激活标签进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <param name="markLabelId"></param>
|
|||
/// <returns></returns>
|
|||
public bool ActivateMarkLabel(long cameraId, long markLabelId); |
|||
|
|||
/// <summary>
|
|||
/// 解除标签进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <param name="markLabelId"></param>
|
|||
/// <returns></returns>
|
|||
public bool DeactivateMarkLabel(long cameraId, long markLabelId); |
|||
|
|||
/// <summary>
|
|||
/// 是否存在标签进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <param name="markLabelId"></param>
|
|||
/// <returns></returns>
|
|||
public bool IsExistsMarkLabel(long cameraId, long markLabelId); |
|||
|
|||
#endregion Base Method
|
|||
|
|||
#region Base Method
|
|||
|
|||
/// <summary>
|
|||
/// 激活相机进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <returns></returns>
|
|||
public Task<bool> ActivateSearcherAsync(long cameraId); |
|||
|
|||
/// <summary>
|
|||
/// 解除相机进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <returns></returns>
|
|||
public Task<bool> DeactivateSearcherAsync(long cameraId); |
|||
|
|||
/// <summary>
|
|||
/// 是否存在相机进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <returns></returns>
|
|||
public Task<bool> IsExistsSearcherAsync(long cameraId); |
|||
|
|||
/// <summary>
|
|||
/// 获取 Searcher
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <returns></returns>
|
|||
public Task<MarkSearcherBase> GetSearcherAsync(long cameraId); |
|||
|
|||
/// <summary>
|
|||
/// 激活标签进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <param name="markLabelId"></param>
|
|||
/// <returns></returns>
|
|||
public Task<bool> ActivateMarkLabelAsync(long cameraId, long markLabelId); |
|||
|
|||
/// <summary>
|
|||
/// 解除标签进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <param name="markLabelId"></param>
|
|||
/// <returns></returns>
|
|||
public Task<bool> DeactivateMarkLabelAsync(long cameraId, long markLabelId); |
|||
|
|||
/// <summary>
|
|||
/// 是否存在标签进入运算
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <param name="markLabelId"></param>
|
|||
/// <returns></returns>
|
|||
public Task<bool> IsExistsMarkLabelAsync(long cameraId, long markLabelId); |
|||
|
|||
#endregion Base Method
|
|||
} |
@ -0,0 +1,268 @@ |
|||
using Cis.Application.Cb; |
|||
using Cis.Application.Cm; |
|||
using Cis.Application.Core.Component.PtzServer; |
|||
using EC.Helper.CameraSDK; |
|||
using StackExchange.Redis; |
|||
using System.Collections.Concurrent; |
|||
|
|||
namespace Cis.Application.Core.Component.MarkSeacher; |
|||
|
|||
/// <summary>
|
|||
/// 追踪标签服务类
|
|||
/// </summary>
|
|||
public class MarkSearcherServer : IMarkSearcherServer, ISingleton |
|||
{ |
|||
#region Attr
|
|||
|
|||
private readonly SqlSugarRepository<CbCamera> _cbCameraRep; |
|||
private readonly SqlSugarRepository<CbCameraParams> _cbCameraParamsRep; |
|||
private readonly SqlSugarRepository<CmMarkLabel> _cmMarkLableRep; |
|||
private readonly IDatabase _cache; |
|||
|
|||
private readonly ICameraSdkServer _cameraSdkServer; |
|||
|
|||
/// <summary>
|
|||
/// {cbCameraId, MarkSearcherBase}
|
|||
/// </summary>
|
|||
private ConcurrentDictionary<long, MarkSearcherBase> MarkSearcherDict { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// MarkSearcherDict 原子操作锁
|
|||
/// </summary>
|
|||
private ReaderWriterLockSlim MsDictRWLock { get; } = new(); |
|||
|
|||
#endregion Attr
|
|||
|
|||
public MarkSearcherServer( |
|||
IDatabase cache, |
|||
ICameraSdkServer cameraSdkServer |
|||
) |
|||
{ |
|||
_cbCameraRep = App.GetService<SqlSugarRepository<CbCamera>>(); |
|||
_cbCameraParamsRep = App.GetService<SqlSugarRepository<CbCameraParams>>(); |
|||
_cmMarkLableRep = App.GetService<SqlSugarRepository<CmMarkLabel>>(); |
|||
_cache = cache; |
|||
_cameraSdkServer = cameraSdkServer; |
|||
} |
|||
|
|||
#region Base Method
|
|||
|
|||
public bool ActivateSearcher(long cameraId) |
|||
{ |
|||
try |
|||
{ |
|||
MsDictRWLock.EnterWriteLock(); |
|||
return ActivateSearcherAtom(cameraId); |
|||
} |
|||
finally |
|||
{ |
|||
MsDictRWLock.ExitWriteLock(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// ActivateSearcher 原子操作
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <returns></returns>
|
|||
private bool ActivateSearcherAtom(long cameraId) |
|||
{ |
|||
bool ret = MarkSearcherDict.ContainsKey(cameraId); |
|||
if (ret) return false; |
|||
// 获取相机
|
|||
CbCamera cbCamera = _cbCameraRep.GetById(cameraId); |
|||
if (cbCamera == null) return false; |
|||
// 获取相机参数
|
|||
CbCameraParams cbCameraParams = _cbCameraParamsRep.GetById(cbCamera.CbCameraParamsId); |
|||
if (cbCameraParams == null) return false; |
|||
|
|||
// 注册并获取 ptz
|
|||
ret = _cameraSdkServer.IsExistsCamera(cbCamera.Ip); |
|||
if (!ret) |
|||
{ |
|||
CameraInfo cameraInfo = CameraInfo.New(cbCameraParams.CameraType, cbCamera.Ip, cbCamera.UserName, cbCamera.Password); |
|||
ret = _cameraSdkServer.RegisterCamera(cameraInfo); |
|||
if (!ret) |
|||
{ |
|||
// double check
|
|||
ret = _cameraSdkServer.IsExistsCamera(cbCamera.Ip); |
|||
if (!ret) return false; |
|||
} |
|||
} |
|||
ret = _cameraSdkServer.GetPtzInfoByIp(cbCamera.Ip, out PtzInfo ptzInfo); |
|||
if (!ret) return false; |
|||
|
|||
// 创建相机计算参数
|
|||
CameraCalcParams cameraCalcParams = CameraCalcParams.New(cbCamera.Id, ptzInfo, cbCameraParams.FocusX, cbCameraParams.FocusY); |
|||
// 创建 markSearcher
|
|||
MarkSearcherBase markSearcher = ((CameraType)cbCameraParams.CameraType).CreateMarkSearcher(cameraCalcParams); |
|||
if (markSearcher == null) return false; |
|||
// 获取标签列表
|
|||
List<CmMarkLabel> cmMarkLabelList = _cmMarkLableRep.GetList(u => u.CbCameraId == cameraId); |
|||
// 将标签添加到 markSearcher
|
|||
foreach (CmMarkLabel item in cmMarkLabelList) |
|||
{ |
|||
// 创建标签计算参数
|
|||
MarkLabelCalcParams labelCalcParams = MarkLabelCalcParams.New( |
|||
item.Id, |
|||
PtzInfo.New(item.PanPosition, item.TiltPosition, item.ZoomPosition), |
|||
item.VideoWidth, |
|||
item.VideoHeight, |
|||
item.CanvasLeftRatio, |
|||
item.CanvasTopRatio |
|||
); |
|||
markSearcher.AddMarkLabelCalcParams(labelCalcParams); |
|||
} |
|||
// 存放 markSearcher
|
|||
ret = MarkSearcherDict.TryAdd(cameraId, markSearcher); |
|||
if (!ret) return false; |
|||
// 记录缓存
|
|||
_cache.HashSet(CacheInfo.CameraId2Ip, cbCamera.Id, cbCamera.Ip); |
|||
_cache.HashIncrement(CacheInfo.CameraIpCounts, cbCamera.Ip); |
|||
return true; |
|||
} |
|||
|
|||
public bool DeactivateSearcher(long cameraId) |
|||
{ |
|||
try |
|||
{ |
|||
MsDictRWLock.EnterWriteLock(); |
|||
return DeactivateSearcherAtom(cameraId); |
|||
} |
|||
finally |
|||
{ |
|||
MsDictRWLock.ExitWriteLock(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// DeactivateSearcher 原子操作
|
|||
/// </summary>
|
|||
/// <param name="cameraId"></param>
|
|||
/// <returns></returns>
|
|||
private bool DeactivateSearcherAtom(long cameraId) |
|||
{ |
|||
bool ret = MarkSearcherDict.TryRemove(cameraId, out _); |
|||
if (!ret) return false; |
|||
RedisValue value = _cache.HashGet(CacheInfo.CameraId2Ip, cameraId); |
|||
if (value.IsNull) return false; |
|||
string cameraIp = (string)value; |
|||
_cache.HashDelete(CacheInfo.CameraId2Ip, cameraId); |
|||
_cache.HashDecrement(CacheInfo.CameraIpCounts, cameraIp); |
|||
//int ipCount = (int)_cache.HashGet(CacheInfo.CameraIpCounts, cameraIp);
|
|||
//if (ipCount <= 0)
|
|||
//{
|
|||
// // 直接删除 or 设置过期
|
|||
// _cache.HashDelete(CacheInfo.CameraIpCounts, cameraIp);
|
|||
// _cameraSdkServer.DeleteCamera(cameraIp);
|
|||
//}
|
|||
return true; |
|||
} |
|||
|
|||
public bool IsExistsSearcher(long cameraId) |
|||
{ |
|||
return MarkSearcherDict.ContainsKey(cameraId); |
|||
} |
|||
|
|||
public MarkSearcherBase GetSearcher(long cameraId) |
|||
{ |
|||
MarkSearcherDict.TryGetValue(cameraId, out MarkSearcherBase searcher); |
|||
return searcher; |
|||
} |
|||
|
|||
public bool ActivateMarkLabel(long cameraId, long markLabelId) |
|||
{ |
|||
bool ret = MarkSearcherDict.TryGetValue(cameraId, out MarkSearcherBase markSearcher); |
|||
if (!ret) return false; |
|||
ret = markSearcher.IsExistsMarkLabelCalcParams(markLabelId); |
|||
if (ret) return false; |
|||
CmMarkLabel label = _cmMarkLableRep.GetById(markLabelId); |
|||
if (label == null) return false; |
|||
MarkLabelCalcParams labelCalcParams = MarkLabelCalcParams.New( |
|||
label.Id, |
|||
PtzInfo.New(label.PanPosition, label.TiltPosition, label.ZoomPosition), |
|||
label.VideoWidth, |
|||
label.VideoHeight, |
|||
label.CanvasLeftRatio, |
|||
label.CanvasTopRatio |
|||
); |
|||
ret = markSearcher.AddMarkLabelCalcParams(labelCalcParams); |
|||
return ret; |
|||
} |
|||
|
|||
public bool DeactivateMarkLabel(long cameraId, long markLabelId) |
|||
{ |
|||
bool ret = MarkSearcherDict.TryGetValue(cameraId, out MarkSearcherBase markSearcher); |
|||
if (!ret) return false; |
|||
ret = markSearcher.DeleteMarkLabelCalcParams(markLabelId); |
|||
return ret; |
|||
} |
|||
|
|||
public bool IsExistsMarkLabel(long cameraId, long markLabelId) |
|||
{ |
|||
return MarkSearcherDict.TryGetValue(cameraId, out MarkSearcherBase searcher) |
|||
&& searcher.IsExistsMarkLabelCalcParams(markLabelId); |
|||
} |
|||
|
|||
#endregion Base Method
|
|||
|
|||
#region Base Method Async
|
|||
|
|||
public async Task<bool> ActivateSearcherAsync(long cameraId) |
|||
{ |
|||
return await Task.Run(() => |
|||
{ |
|||
return ActivateSearcher(cameraId); |
|||
}); |
|||
} |
|||
|
|||
public async Task<bool> DeactivateSearcherAsync(long cameraId) |
|||
{ |
|||
return await Task.Run(() => |
|||
{ |
|||
return DeactivateSearcher(cameraId); |
|||
}); |
|||
} |
|||
|
|||
public async Task<bool> IsExistsSearcherAsync(long cameraId) |
|||
{ |
|||
return await Task.Run(() => |
|||
{ |
|||
return IsExistsSearcher(cameraId); |
|||
}); |
|||
} |
|||
|
|||
public async Task<MarkSearcherBase> GetSearcherAsync(long cameraId) |
|||
{ |
|||
return await Task.Run(() => |
|||
{ |
|||
return GetSearcher(cameraId); |
|||
}); |
|||
} |
|||
|
|||
public async Task<bool> ActivateMarkLabelAsync(long cameraId, long markLabelId) |
|||
{ |
|||
return await Task.Run(() => |
|||
{ |
|||
return ActivateMarkLabel(cameraId, markLabelId); |
|||
}); |
|||
} |
|||
|
|||
public async Task<bool> DeactivateMarkLabelAsync(long cameraId, long markLabelId) |
|||
{ |
|||
return await Task.Run(() => |
|||
{ |
|||
return DeactivateMarkLabel(cameraId, markLabelId); |
|||
}); |
|||
} |
|||
|
|||
public async Task<bool> IsExistsMarkLabelAsync(long cameraId, long markLabelId) |
|||
{ |
|||
return await Task.Run(() => |
|||
{ |
|||
return IsExistsMarkLabel(cameraId, markLabelId); |
|||
}); |
|||
} |
|||
|
|||
#endregion Base Method Async
|
|||
} |
@ -0,0 +1,31 @@ |
|||
using System.Drawing; |
|||
|
|||
namespace Cis.Application.Core.Component.MarkSeacher; |
|||
|
|||
public class DaHuaMarkSearcher : MarkSearcherBase |
|||
{ |
|||
public DaHuaMarkSearcher(CameraCalcParams cameraCalcParams) : base(cameraCalcParams) |
|||
{ |
|||
} |
|||
|
|||
#region Implement
|
|||
|
|||
protected override double ConvertPanPosToAngle(double panPos) |
|||
{ |
|||
double ret = (-0.1 * panPos) / 180 * Math.PI; |
|||
return ret; |
|||
} |
|||
|
|||
protected override double ConvertTiltPosToAngle(double tiltPos, double tiltMinPos = 0) |
|||
{ |
|||
double ret = (0.1 * tiltPos) / 180 * Math.PI; |
|||
return ret; |
|||
} |
|||
|
|||
protected override PointF GetFOfMatrixByZoomPos(double zoomPos) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
#endregion Implement
|
|||
} |
@ -0,0 +1,73 @@ |
|||
using System.Drawing; |
|||
|
|||
namespace Cis.Application.Core.Component.MarkSeacher; |
|||
|
|||
public class HiKMarkSearcher : MarkSearcherBase |
|||
{ |
|||
public HiKMarkSearcher(CameraCalcParams cameraCalcParams) : base(cameraCalcParams) |
|||
{ |
|||
} |
|||
|
|||
#region Implement
|
|||
|
|||
protected override double ConvertPanPosToAngle(double panPos) |
|||
{ |
|||
double ret = 0.1 * HexToDecMa(panPos) / 180 * Math.PI; |
|||
ret = (ret >= 0) ? ret : (2 * Math.PI + ret); |
|||
return ret; |
|||
} |
|||
|
|||
protected override double ConvertTiltPosToAngle(double tiltPos, double tiltMinPos = 0) |
|||
{ |
|||
double ndiff; |
|||
if (tiltPos > tiltMinPos) |
|||
|
|||
ndiff = HexToDecMa(tiltPos) - HexToDecMa(tiltMinPos); |
|||
else |
|||
ndiff = HexToDecMa(tiltPos) + HexToDecMa(13824) - HexToDecMa(tiltMinPos); |
|||
double ret = 0.1 * ndiff / 180 * Math.PI; |
|||
return ret; |
|||
} |
|||
|
|||
protected override PointF GetFOfMatrixByZoomPos(double zoomPos) |
|||
{ |
|||
PointF pointF = new() |
|||
{ |
|||
X = (float)GetFx(zoomPos), |
|||
Y = (float)GetFy(zoomPos) |
|||
}; |
|||
return pointF; |
|||
} |
|||
|
|||
#endregion Implement
|
|||
|
|||
#region Util
|
|||
|
|||
protected virtual double GetFx(double zoomPos) |
|||
{ |
|||
CameraCalcParams calcParams = CameraCalcParams; |
|||
return calcParams.FocusX * GetZoomTag(zoomPos); |
|||
} |
|||
|
|||
protected virtual double GetFy(double zoomPos) |
|||
{ |
|||
CameraCalcParams calcParams = CameraCalcParams; |
|||
return calcParams.FocusY * GetZoomTag(zoomPos); |
|||
} |
|||
|
|||
protected virtual double GetZoomTag(double zoomPos) |
|||
{ |
|||
double ret = HexToDecMa(zoomPos) * 0.1; |
|||
ret = (ret - 1) * 0.65 + 1; |
|||
return ret; |
|||
} |
|||
|
|||
protected int HexToDecMa(double wHex) |
|||
{ |
|||
int wHexInt = wHex.ToInt(); |
|||
int ret = (wHexInt / 4096) * 1000 + ((wHexInt % 4096) / 256) * 100 + ((wHexInt % 256) / 16) * 10 + (wHexInt % 16); |
|||
return ret; |
|||
} |
|||
|
|||
#endregion Util
|
|||
} |
@ -0,0 +1,254 @@ |
|||
using EC.Helper.CameraSDK; |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.LinearAlgebra.Double; |
|||
using System.Collections.Concurrent; |
|||
using System.Drawing; |
|||
|
|||
namespace Cis.Application.Core.Component.MarkSeacher; |
|||
|
|||
public abstract class MarkSearcherBase |
|||
{ |
|||
#region Attr
|
|||
|
|||
/// <summary>
|
|||
/// 当前相机计算参数
|
|||
/// </summary>
|
|||
protected CameraCalcParams CameraCalcParams { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 相机当前位置的世界坐标转化为相机坐标矩阵
|
|||
/// </summary>
|
|||
protected Matrix<double> World2CameraMatrix { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// {cameraId, MarkLabelCalcParams}
|
|||
/// </summary>
|
|||
private ConcurrentDictionary<long, MarkLabelCalcParams> MarkLabelCalcParamsDict { get; set; } = new(); |
|||
|
|||
#endregion Attr
|
|||
|
|||
public MarkSearcherBase(CameraCalcParams cameraCalcParams) |
|||
{ |
|||
CameraCalcParams = cameraCalcParams; |
|||
CalcSensor(); |
|||
World2CameraMatrix = ConvertWorldToCamera(cameraCalcParams); |
|||
} |
|||
|
|||
#region Calc
|
|||
|
|||
/// <summary>
|
|||
/// 判断相机是否进行了转动,转动了则需要重新计算世界坐标到相机坐标的转换矩阵
|
|||
/// </summary>
|
|||
/// <param name="newInfo"></param>
|
|||
/// <returns></returns>
|
|||
protected bool IsCameraRotate(PtzInfo newInfo) |
|||
{ |
|||
bool ret = CameraCalcParams.PtzInfo.Pan != newInfo.Pan || |
|||
CameraCalcParams.PtzInfo.Tilt != newInfo.Tilt || |
|||
CameraCalcParams.PtzInfo.Zoom != newInfo.Zoom; |
|||
return ret; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 此方法计算在球机zoom值最小的情况下成像矩阵中的 f 本质为获取像元大小
|
|||
/// 尝试方案1:通过计算的方式来获取
|
|||
/// 尝试方案2:通过张正友相机标定的方法来生成成像矩阵中的 f
|
|||
/// </summary>
|
|||
protected void CalcSensor() |
|||
{ |
|||
CameraCalcParams.FocusX /= CameraCalcParams.VideoWidth; |
|||
CameraCalcParams.FocusY /= CameraCalcParams.VideoHeight; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取将世界坐标系中的点转化为相机坐标系中的点的转换矩阵
|
|||
/// </summary>
|
|||
/// <param name="cameraCalcParams"></param>
|
|||
/// <returns></returns>
|
|||
protected Matrix<double> ConvertWorldToCamera(CameraCalcParams cameraCalcParams) |
|||
{ |
|||
double panAngle = ConvertPanPosToAngle(cameraCalcParams.PtzInfo.Pan); |
|||
double tiltAngle = ConvertTiltPosToAngle(cameraCalcParams.PtzInfo.Tilt); |
|||
PointF pointF = GetFOfMatrixByZoomPos(cameraCalcParams.PtzInfo.Zoom); |
|||
double sinPan = Math.Sin(panAngle); |
|||
double cosPan = Math.Cos(panAngle); |
|||
double sinTilt = Math.Sin(tiltAngle); |
|||
double cosTilt = Math.Cos(tiltAngle); |
|||
|
|||
Matrix<double> fMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
pointF.X, 0, 0, |
|||
0, pointF.Y, 0, |
|||
0, 0, 1, |
|||
}); |
|||
|
|||
Matrix<double> rotateTiltMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
1, 0, 0, |
|||
0, cosTilt, sinTilt, |
|||
0, -sinTilt, cosTilt, |
|||
}); |
|||
|
|||
Matrix<double> rotatePanMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
cosPan, 0, sinPan, |
|||
0, 1, 0, |
|||
-sinPan, 0, cosPan, |
|||
}); |
|||
|
|||
Matrix<double> resultMatrix = fMatrix.Multiply(rotateTiltMatrix).Multiply(rotatePanMatrix); |
|||
return resultMatrix; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取将相机坐标系中的点转化为世界坐标系中的点的转换矩阵
|
|||
/// </summary>
|
|||
/// <param name="labelCalcParams"></param>
|
|||
/// <returns></returns>
|
|||
protected Matrix<double> ConvertCameraToWorld(MarkLabelCalcParams labelCalcParams) |
|||
{ |
|||
double panAngle = ConvertPanPosToAngle(labelCalcParams.PtzInfo.Pan); |
|||
double tiltAngle = ConvertTiltPosToAngle(labelCalcParams.PtzInfo.Tilt); |
|||
PointF pointF = GetFOfMatrixByZoomPos(labelCalcParams.PtzInfo.Zoom); |
|||
double sinPan = Math.Sin(panAngle); |
|||
double cosPan = Math.Cos(panAngle); |
|||
double sinTilt = Math.Sin(tiltAngle); |
|||
double cosTilt = Math.Cos(tiltAngle); |
|||
|
|||
Matrix<double> rotatePanMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
cosPan, 0, -sinPan, |
|||
0, 1, 0, |
|||
sinPan, 0, cosPan, |
|||
}); |
|||
|
|||
Matrix<double> rotateTiltMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
1, 0, 0, |
|||
0, cosTilt, -sinTilt, |
|||
0, sinTilt, cosTilt, |
|||
}); |
|||
|
|||
Matrix<double> fMatrix = new DenseMatrix(3, 3, new double[] |
|||
{ |
|||
(1 / pointF.X), 0, 0, |
|||
0, (1 / pointF.Y), 0, |
|||
0, 0, 1, |
|||
}); |
|||
|
|||
Matrix<double> resultMatrix = rotatePanMatrix.Multiply(rotateTiltMatrix).Multiply(fMatrix); |
|||
return resultMatrix; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 计算标签位置过程
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public List<MarkLabelCalcResult> Search() |
|||
{ |
|||
List<MarkLabelCalcResult> resultList = new(); |
|||
|
|||
if (World2CameraMatrix == null || MarkLabelCalcParamsDict.IsEmpty) |
|||
return resultList; |
|||
|
|||
foreach (MarkLabelCalcParams item in MarkLabelCalcParamsDict.Values) |
|||
{ |
|||
Matrix<double> labelC2WMatrix = ConvertCameraToWorld(item); |
|||
Matrix<double> labelPointMatrix = new DenseMatrix(3, 1, new double[] |
|||
{ |
|||
(item.CanvasLeftRatio-0.5)*item.VideoWidth / CameraCalcParams.VideoWidth, |
|||
(item.CanvasTopRatio-0.5)*item.VideoHeight / CameraCalcParams.VideoHeight, |
|||
1 |
|||
}); |
|||
Matrix<double> lResult = labelC2WMatrix.Multiply(labelPointMatrix); |
|||
Matrix<double> pResult = World2CameraMatrix.Multiply(lResult); |
|||
|
|||
double x = pResult[0, 0] / pResult[2, 0] + 0.5; |
|||
double y = pResult[1, 0] / pResult[2, 0] + 0.5; |
|||
MarkLabelCalcResult labelCalcResult; |
|||
if (x > 0.99 || x < 0.01 || y > 0.99 || y < 0.01 || pResult[2, 0] < 0) |
|||
labelCalcResult = MarkLabelCalcResult.New(item.Id, false); |
|||
else |
|||
labelCalcResult = MarkLabelCalcResult.New(item.Id, true, x, y); |
|||
resultList.Add(labelCalcResult); |
|||
} |
|||
return resultList; |
|||
} |
|||
|
|||
public async Task<List<MarkLabelCalcResult>> SearchAsync() |
|||
{ |
|||
return await Task.Run(Search); |
|||
} |
|||
|
|||
#endregion Calc
|
|||
|
|||
#region Util
|
|||
|
|||
/// <summary>
|
|||
/// 将Pan值转化为角度
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
protected abstract double ConvertPanPosToAngle(double panPos); |
|||
|
|||
/// <summary>
|
|||
/// 将Tilt转化为角度
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
protected abstract double ConvertTiltPosToAngle(double tiltPos, double tiltMinPos = 0); |
|||
|
|||
/// <summary>
|
|||
/// 根据当前zoom值获取相机矩阵参数
|
|||
/// </summary>
|
|||
/// <param name="zoomPos"></param>
|
|||
/// <returns></returns>
|
|||
protected abstract PointF GetFOfMatrixByZoomPos(double zoomPos); |
|||
|
|||
#endregion Util
|
|||
|
|||
#region Base Method
|
|||
|
|||
/// <summary>
|
|||
/// 更新相机计算参数
|
|||
/// </summary>
|
|||
/// <param name="ptzInfo"></param>
|
|||
public void UpdateCameraCalcParams(PtzInfo ptzInfo) |
|||
{ |
|||
if (IsCameraRotate(ptzInfo)) |
|||
{ |
|||
CameraCalcParams.PtzInfo = ptzInfo; |
|||
World2CameraMatrix = ConvertWorldToCamera(CameraCalcParams); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 添加标签计算参数
|
|||
/// </summary>
|
|||
/// <param name="labelCalcParams"></param>
|
|||
/// <returns></returns>
|
|||
public bool AddMarkLabelCalcParams(MarkLabelCalcParams labelCalcParams) |
|||
{ |
|||
return MarkLabelCalcParamsDict.TryAdd(labelCalcParams.Id, labelCalcParams); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 删除标签计算参数
|
|||
/// </summary>
|
|||
/// <param name="markLabelId"></param>
|
|||
/// <returns></returns>
|
|||
public bool DeleteMarkLabelCalcParams(long markLabelId) |
|||
{ |
|||
return MarkLabelCalcParamsDict.TryRemove(markLabelId, out _); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 是否存在标签计算参数
|
|||
/// </summary>
|
|||
/// <param name="markLabelId"></param>
|
|||
/// <returns></returns>
|
|||
public bool IsExistsMarkLabelCalcParams(long markLabelId) |
|||
{ |
|||
return MarkLabelCalcParamsDict.ContainsKey(markLabelId); |
|||
} |
|||
|
|||
#endregion Base Method
|
|||
} |
@ -0,0 +1,29 @@ |
|||
using System.Drawing; |
|||
|
|||
namespace Cis.Application.Core.Component.MarkSeacher; |
|||
|
|||
public class YuShiMarkSearcher : MarkSearcherBase |
|||
{ |
|||
public YuShiMarkSearcher(CameraCalcParams cameraCalcParams) : base(cameraCalcParams) |
|||
{ |
|||
} |
|||
|
|||
#region Implement
|
|||
|
|||
protected override double ConvertPanPosToAngle(double panPos) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
protected override double ConvertTiltPosToAngle(double tiltPos, double tiltMinPos = 0) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
protected override PointF GetFOfMatrixByZoomPos(double zoomPos) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
#endregion Implement
|
|||
} |
@ -1,181 +0,0 @@ |
|||
namespace Cis.Application.Core; |
|||
|
|||
/// <summary>
|
|||
/// 相机计算信息
|
|||
/// </summary>
|
|||
public class CameraCalcInfo |
|||
{ |
|||
/// <summary>
|
|||
/// Camera Id
|
|||
/// </summary>
|
|||
public long Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Ptz 信息
|
|||
/// </summary>
|
|||
public PtzInfo PtzInfo { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 视频的宽度
|
|||
/// </summary>
|
|||
public int VideoWidth { get; set; } = 1920; |
|||
|
|||
/// <summary>
|
|||
/// 视频的高度
|
|||
/// </summary>
|
|||
public int VideoHeight { get; set; } = 1080; |
|||
|
|||
/// <summary>
|
|||
/// 最小焦距
|
|||
/// </summary>
|
|||
public double MinFocusX { get; set; } = 0f; |
|||
|
|||
public double MinFocusY { get; set; } = 0f; |
|||
|
|||
public static CameraCalcInfo New(long id, PtzInfo ptzInfo) |
|||
{ |
|||
return new() |
|||
{ |
|||
Id = id, |
|||
PtzInfo = ptzInfo |
|||
}; |
|||
} |
|||
|
|||
public static CameraCalcInfo New(long id, PtzInfo ptzInfo, int videoWidth, int videoHeight) |
|||
{ |
|||
return new() |
|||
{ |
|||
Id = id, |
|||
PtzInfo = ptzInfo, |
|||
VideoWidth = videoWidth, |
|||
VideoHeight = videoHeight |
|||
}; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Ptz 信息
|
|||
/// </summary>
|
|||
public class PtzInfo |
|||
{ |
|||
/// <summary>
|
|||
/// Pan 坐标
|
|||
/// </summary>
|
|||
public double Pan { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Tilt 坐标
|
|||
/// </summary>
|
|||
public double Tilt { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Zoom 坐标
|
|||
/// </summary>
|
|||
public double Zoom { get; set; } |
|||
|
|||
public static PtzInfo New(double pan, double tilt, double zoom) |
|||
{ |
|||
return new() |
|||
{ |
|||
Pan = pan, |
|||
Tilt = tilt, |
|||
Zoom = zoom |
|||
}; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 标记标签计算信息
|
|||
/// </summary>
|
|||
public class MarkLabelCalcInfo |
|||
{ |
|||
/// <summary>
|
|||
/// MarkLabel Id
|
|||
/// </summary>
|
|||
public long Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Ptz 信息
|
|||
/// </summary>
|
|||
public PtzInfo PtzInfo { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 视频宽度
|
|||
/// </summary>
|
|||
public double VideoWidth { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 视频高度
|
|||
/// </summary>
|
|||
public double VideoHeight { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 画布 left 距离比例
|
|||
/// </summary>
|
|||
public double CanvasLeftRatio { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 画布 top 距离比例
|
|||
/// </summary>
|
|||
public double CanvasTopRatio { get; set; } |
|||
|
|||
public static MarkLabelCalcInfo New(long id, PtzInfo ptzInfo, double videoWidth, double videoHeight, double canvasLeftRatio, double canvasTopRatio) |
|||
{ |
|||
return new() |
|||
{ |
|||
Id = id, |
|||
PtzInfo = ptzInfo, |
|||
VideoWidth = videoWidth, |
|||
VideoHeight = videoHeight, |
|||
CanvasLeftRatio = canvasLeftRatio, |
|||
CanvasTopRatio = canvasTopRatio |
|||
}; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 标记标签计算结果
|
|||
/// </summary>
|
|||
public class MarkLabelCalcResult |
|||
{ |
|||
/// <summary>
|
|||
/// MarkLabel Id
|
|||
/// </summary>
|
|||
public long Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// true 显示(在当前视频画面里面)
|
|||
/// false 不显示(不在当前视频画面里面)
|
|||
/// </summary>
|
|||
public bool InFlag { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 画布 left 距离比例
|
|||
/// </summary>
|
|||
public double CanvasLeftRatio { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 画布 top 距离比例
|
|||
/// </summary>
|
|||
public double CanvasTopRatio { get; set; } |
|||
|
|||
public static MarkLabelCalcResult New(long id, bool inFlag) |
|||
{ |
|||
return new() |
|||
{ |
|||
Id = id, |
|||
InFlag = inFlag |
|||
}; |
|||
} |
|||
|
|||
public static MarkLabelCalcResult New(long id, bool inFlag, double canvasLeftRatio, double canvasTopRatio) |
|||
{ |
|||
return new() |
|||
{ |
|||
Id = id, |
|||
InFlag = inFlag, |
|||
CanvasLeftRatio = canvasLeftRatio, |
|||
CanvasTopRatio = canvasTopRatio |
|||
}; |
|||
} |
|||
} |
@ -1,22 +0,0 @@ |
|||
namespace Cis.Application.Tb; |
|||
|
|||
public class TbInfo |
|||
{ |
|||
#region Database Info
|
|||
|
|||
/// <summary>
|
|||
/// 数据库标识
|
|||
/// </summary>
|
|||
public const string DbName = "serverdb"; |
|||
|
|||
#endregion Database Info
|
|||
|
|||
#region Table Info
|
|||
|
|||
/// <summary>
|
|||
/// TbPtzCamera 表名
|
|||
/// </summary>
|
|||
public const string TbPtzCameraTbName = "tb_ptzcamera"; |
|||
|
|||
#endregion Table Info
|
|||
} |
@ -1,28 +0,0 @@ |
|||
namespace Cis.Application.Tb; |
|||
|
|||
[SugarTable(TbInfo.TbPtzCameraTbName)] |
|||
[Tenant(TbInfo.DbName)] |
|||
public class TbPtzCamera |
|||
{ |
|||
public int Id { get; set; } |
|||
|
|||
public int Type { get; set; } |
|||
|
|||
public string Ip { get; set; } |
|||
|
|||
public int Port { get; set; } |
|||
|
|||
public string User { get; set; } |
|||
|
|||
public string Pass { get; set; } |
|||
|
|||
public int CameraId { get; set; } |
|||
|
|||
public double PanPosition { get; set; } |
|||
|
|||
public double TitlePosition { get; set; } |
|||
|
|||
public double ZoomPosition { get; set; } |
|||
|
|||
public byte Request { get; set; } |
|||
} |
@ -1,57 +0,0 @@ |
|||
using Newtonsoft.Json.Linq; |
|||
|
|||
namespace Cis.Application.Tb; |
|||
|
|||
public class TbPtzCameraService : ITransient |
|||
{ |
|||
private readonly SqlSugarRepository<TbPtzCamera> _tbPtzCameraRep; |
|||
|
|||
public TbPtzCameraService(SqlSugarRepository<TbPtzCamera> tbPtzCameraRep) |
|||
{ |
|||
_tbPtzCameraRep = tbPtzCameraRep; |
|||
} |
|||
|
|||
[HttpPost] |
|||
public async Task<bool> Add([FromForm] TbPtzCamera entity) |
|||
{ |
|||
return await _tbPtzCameraRep.InsertAsync(entity); |
|||
} |
|||
|
|||
[HttpPost] |
|||
public async Task<bool> Update([FromForm] TbPtzCamera entity) |
|||
{ |
|||
return await _tbPtzCameraRep.UpdateAsync(entity); |
|||
} |
|||
|
|||
[HttpPost] |
|||
public async Task<bool> Delete([FromForm] TbPtzCamera entity) |
|||
{ |
|||
return await _tbPtzCameraRep.DeleteAsync(entity); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public async Task<TbPtzCamera> Get(int id) |
|||
{ |
|||
TbPtzCamera entity = await _tbPtzCameraRep.GetByIdAsync(id); |
|||
return entity; |
|||
} |
|||
|
|||
[HttpGet] |
|||
public async Task<List<TbPtzCamera>> GetList(string queryJson = "") |
|||
{ |
|||
JObject queryObj = !string.IsNullOrEmpty(queryJson) ? queryJson.ToJObject() : default; |
|||
List<TbPtzCamera> list = await _tbPtzCameraRep.AsQueryable() |
|||
.ToListAsync(); |
|||
return list; |
|||
} |
|||
|
|||
[HttpGet] |
|||
public async Task<List<TbPtzCamera>> GetPageList(string pagination, string queryJson = "") |
|||
{ |
|||
Pagination pageObj = pagination.ToObject<Pagination>(); |
|||
JObject queryObj = queryJson.ToJObject(); |
|||
List<TbPtzCamera> list = await _tbPtzCameraRep.AsQueryable() |
|||
.ToPageListAsync(pageObj.Index, pageObj.Size); |
|||
return list; |
|||
} |
|||
} |
@ -1,28 +1,21 @@ |
|||
using NewLife.Caching; |
|||
using StackExchange.Redis; |
|||
|
|||
namespace Cis.Core; |
|||
|
|||
public static class CacheSetup |
|||
{ |
|||
/// <summary>
|
|||
/// 缓存注册(新生命Redis组件)
|
|||
/// Redis 缓存注册
|
|||
/// </summary>
|
|||
/// <param name="services"></param>
|
|||
public static void AddCache(this IServiceCollection services) |
|||
{ |
|||
services.AddSingleton<ICache>(options => |
|||
services.AddSingleton<IDatabase>(options => |
|||
{ |
|||
var cacheOptions = App.GetOptions<CacheOptions>(); |
|||
if (cacheOptions.CacheType == CacheTypeEnum.Redis.ToString()) |
|||
{ |
|||
var redis = new Redis(); |
|||
redis.Init(cacheOptions.RedisConnectionString); |
|||
return redis; |
|||
} |
|||
else |
|||
{ |
|||
return Cache.Default; |
|||
} |
|||
var redisOptions = App.GetOptions<RedisOptions>(); |
|||
ConnectionMultiplexer multiplexer = ConnectionMultiplexer.Connect(redisOptions.ConnectionString); |
|||
IDatabase cache = multiplexer.GetDatabase(); |
|||
return cache; |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
namespace Cis.Core; |
|||
|
|||
public class SqlSugarConst |
|||
{ |
|||
/// <summary>
|
|||
/// 默认数据库标识
|
|||
/// </summary>
|
|||
public const string DefaultConfigId = "Cis"; |
|||
|
|||
/// <summary>
|
|||
/// 默认表主键
|
|||
/// </summary>
|
|||
public const string DefaultPrimaryKey = "Id"; |
|||
} |
@ -0,0 +1,61 @@ |
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 框架实体基类Id
|
|||
/// </summary>
|
|||
public abstract class EntityBaseId |
|||
{ |
|||
/// <summary>
|
|||
/// 雪花Id
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true, IsIdentity = false)] |
|||
public virtual long Id { get; set; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 框架实体基类
|
|||
/// </summary>
|
|||
public abstract class EntityBase : EntityBaseId |
|||
{ |
|||
/// <summary>
|
|||
/// 创建时间
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "创建时间")] |
|||
public virtual DateTime? CreateTime { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 更新时间
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "更新时间")] |
|||
public virtual DateTime? UpdateTime { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 创建者Id
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "创建者Id")] |
|||
public virtual long? CreateUserId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 修改者Id
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "修改者Id")] |
|||
public virtual long? UpdateUserId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 软删除
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "软删除")] |
|||
public virtual bool IsDelete { get; set; } = false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 业务数据实体基类(数据权限)
|
|||
/// </summary>
|
|||
public abstract class DataEntityBase : EntityBase |
|||
{ |
|||
/// <summary>
|
|||
/// 创建者部门Id
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "创建者部门Id")] |
|||
public virtual long? CreateOrgId { get; set; } |
|||
} |
@ -0,0 +1,47 @@ |
|||
namespace Cis.Core; |
|||
|
|||
public class Pagination |
|||
{ |
|||
/// <summary>
|
|||
/// 当前页
|
|||
/// </summary>
|
|||
public virtual int Index { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 页码容量
|
|||
/// </summary>
|
|||
public virtual int Size { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 排序字段
|
|||
/// </summary>
|
|||
public virtual string Field { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 排序方向
|
|||
/// </summary>
|
|||
public virtual string Order { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 总记录数
|
|||
/// </summary>
|
|||
public virtual int Total { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 页码数
|
|||
/// </summary>
|
|||
public virtual int Number |
|||
{ |
|||
get |
|||
{ |
|||
if (Size > 0) |
|||
{ |
|||
return ((Size % Total) == 0) ? (Size / Total) : ((Size / Total) + 1); |
|||
} |
|||
else |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,128 @@ |
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 全局规范化结果
|
|||
/// </summary>
|
|||
[UnifyModel(typeof(RespParam<>))] |
|||
public class RespParamProvider : IUnifyResultProvider |
|||
{ |
|||
/// <summary>
|
|||
/// 异常返回值
|
|||
/// </summary>
|
|||
/// <param name="context"></param>
|
|||
/// <param name="metadata"></param>
|
|||
/// <returns></returns>
|
|||
public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata) |
|||
{ |
|||
return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 成功返回值
|
|||
/// </summary>
|
|||
/// <param name="context"></param>
|
|||
/// <param name="data"></param>
|
|||
/// <returns></returns>
|
|||
public IActionResult OnSucceeded(ActionExecutedContext context, object data) |
|||
{ |
|||
return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 验证失败返回值
|
|||
/// </summary>
|
|||
/// <param name="context"></param>
|
|||
/// <param name="metadata"></param>
|
|||
/// <returns></returns>
|
|||
public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata) |
|||
{ |
|||
return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.ValidationResult)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 特定状态码返回值
|
|||
/// </summary>
|
|||
/// <param name="context"></param>
|
|||
/// <param name="statusCode"></param>
|
|||
/// <param name="unifyResultSettings"></param>
|
|||
/// <returns></returns>
|
|||
public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings) |
|||
{ |
|||
// 设置响应状态码
|
|||
UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings); |
|||
|
|||
switch (statusCode) |
|||
{ |
|||
// 处理 401 状态码
|
|||
case StatusCodes.Status401Unauthorized: |
|||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "401 登录已过期,请重新登录"), |
|||
App.GetOptions<JsonOptions>()?.JsonSerializerOptions); |
|||
break; |
|||
// 处理 403 状态码
|
|||
case StatusCodes.Status403Forbidden: |
|||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "403 禁止访问,没有权限"), |
|||
App.GetOptions<JsonOptions>()?.JsonSerializerOptions); |
|||
break; |
|||
|
|||
default: break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 返回 RESTful 风格结果集
|
|||
/// </summary>
|
|||
/// <param name="statusCode"></param>
|
|||
/// <param name="succeeded"></param>
|
|||
/// <param name="data"></param>
|
|||
/// <param name="errors"></param>
|
|||
/// <returns></returns>
|
|||
private static RespParam<object> RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default) |
|||
{ |
|||
return new RespParam<object> |
|||
{ |
|||
Code = statusCode, |
|||
Message = errors is string str ? str : JSON.Serialize(errors), |
|||
Data = data, |
|||
Type = succeeded ? "success" : "error", |
|||
Extras = UnifyContext.Take(), |
|||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() |
|||
}; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 全局返回结果
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
public class RespParam<T> |
|||
{ |
|||
/// <summary>
|
|||
/// 状态码
|
|||
/// </summary>
|
|||
public int Code { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 类型success、warning、error
|
|||
/// </summary>
|
|||
public string Type { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 错误信息
|
|||
/// </summary>
|
|||
public string Message { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 数据
|
|||
/// </summary>
|
|||
public T Data { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 附加数据
|
|||
/// </summary>
|
|||
public object Extras { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 时间戳
|
|||
/// </summary>
|
|||
public long Timestamp { get; set; } |
|||
} |
@ -0,0 +1,19 @@ |
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 通用状态枚举
|
|||
/// </summary>
|
|||
public enum StatusEnum |
|||
{ |
|||
/// <summary>
|
|||
/// 停用
|
|||
/// </summary>
|
|||
[Description("停用")] |
|||
Disable = 0, |
|||
|
|||
/// <summary>
|
|||
/// 启用
|
|||
/// </summary>
|
|||
[Description("启用")] |
|||
Enable = 1, |
|||
} |
@ -0,0 +1,141 @@ |
|||
using Newtonsoft.Json.Linq; |
|||
using System.Text; |
|||
|
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 对象拓展
|
|||
/// </summary>
|
|||
[SuppressSniffer] |
|||
public static class ObjectExtension |
|||
{ |
|||
/// <summary>
|
|||
/// 判断类型是否实现某个泛型
|
|||
/// </summary>
|
|||
/// <param name="type">类型</param>
|
|||
/// <param name="generic">泛型类型</param>
|
|||
/// <returns>bool</returns>
|
|||
public static bool HasImplementedRawGeneric(this Type type, Type generic) |
|||
{ |
|||
// 检查接口类型
|
|||
var isTheRawGenericType = type.GetInterfaces().Any(IsTheRawGenericType); |
|||
if (isTheRawGenericType) return true; |
|||
|
|||
// 检查类型
|
|||
while (type != null && type != typeof(object)) |
|||
{ |
|||
isTheRawGenericType = IsTheRawGenericType(type); |
|||
if (isTheRawGenericType) return true; |
|||
type = type.BaseType; |
|||
} |
|||
|
|||
return false; |
|||
|
|||
// 判断逻辑
|
|||
bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将字典转化为QueryString格式
|
|||
/// </summary>
|
|||
/// <param name="dict"></param>
|
|||
/// <param name="urlEncode"></param>
|
|||
/// <returns></returns>
|
|||
public static string ToQueryString(this Dictionary<string, string> dict, bool urlEncode = true) |
|||
{ |
|||
return string.Join("&", dict.Select(p => $"{(urlEncode ? p.Key?.UrlEncode() : "")}={(urlEncode ? p.Value?.UrlEncode() : "")}")); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将字符串URL编码
|
|||
/// </summary>
|
|||
/// <param name="str"></param>
|
|||
/// <returns></returns>
|
|||
public static string UrlEncode(this string str) |
|||
{ |
|||
return string.IsNullOrEmpty(str) ? "" : System.Web.HttpUtility.UrlEncode(str, Encoding.UTF8); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// List转DataTable
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
/// <param name="list"></param>
|
|||
/// <returns></returns>
|
|||
public static DataTable ToDataTable<T>(this List<T> list) |
|||
{ |
|||
DataTable result = new(); |
|||
if (list.Count > 0) |
|||
{ |
|||
// result.TableName = list[0].GetType().Name; // 表名赋值
|
|||
PropertyInfo[] propertys = list[0].GetType().GetProperties(); |
|||
foreach (PropertyInfo pi in propertys) |
|||
{ |
|||
Type colType = pi.PropertyType; |
|||
if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>)) |
|||
{ |
|||
colType = colType.GetGenericArguments()[0]; |
|||
} |
|||
if (IsIgnoreColumn(pi)) |
|||
continue; |
|||
result.Columns.Add(pi.Name, colType); |
|||
} |
|||
for (int i = 0; i < list.Count; i++) |
|||
{ |
|||
ArrayList tempList = new(); |
|||
foreach (PropertyInfo pi in propertys) |
|||
{ |
|||
if (IsIgnoreColumn(pi)) |
|||
continue; |
|||
object obj = pi.GetValue(list[i], null); |
|||
tempList.Add(obj); |
|||
} |
|||
object[] array = tempList.ToArray(); |
|||
result.LoadDataRow(array, true); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 对象序列化成Json字符串
|
|||
/// </summary>
|
|||
/// <param name="obj"></param>
|
|||
/// <returns></returns>
|
|||
public static string ToJson(object obj) |
|||
{ |
|||
return JSON.GetJsonSerializer().Serialize(obj); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Json字符串反序列化成对象
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
/// <param name="json"></param>
|
|||
/// <returns></returns>
|
|||
public static T ToObject<T>(this string json) |
|||
{ |
|||
return JSON.GetJsonSerializer().Deserialize<T>(json); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 字串反序列化成linq对象
|
|||
/// </summary>
|
|||
/// <param name="Json">字串</param>
|
|||
/// <returns></returns>
|
|||
public static JObject ToJObject(this string Json) |
|||
{ |
|||
return string.IsNullOrEmpty(Json) ? JObject.Parse("{}") : JObject.Parse(Json.Replace(" ", "")); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 排除SqlSugar忽略的列
|
|||
/// </summary>
|
|||
/// <param name="pi"></param>
|
|||
/// <returns></returns>
|
|||
private static bool IsIgnoreColumn(PropertyInfo pi) |
|||
{ |
|||
var sc = pi.GetCustomAttributes<SugarColumn>(false).FirstOrDefault(u => u.IsIgnore == true); |
|||
return sc != null; |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 数据库配置选项
|
|||
/// </summary>
|
|||
public sealed class DbConnectionOptions : IConfigurableOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 数据库集合
|
|||
/// </summary>
|
|||
public List<DbConnectionConfig> ConnectionConfigs { get; set; } |
|||
} |
|||
|
|||
public sealed class DbConnectionConfig : ConnectionConfig |
|||
{ |
|||
/// <summary>
|
|||
/// 启用库表初始化
|
|||
/// </summary>
|
|||
public bool EnableInitDb { get; set; } |
|||
} |
@ -0,0 +1,12 @@ |
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// Redis 配置选项
|
|||
/// </summary>
|
|||
public sealed class RedisOptions : IConfigurableOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Redis 连接字符串
|
|||
/// </summary>
|
|||
public string ConnectionString { get; set; } |
|||
} |
@ -1,14 +0,0 @@ |
|||
namespace Cis.Core; |
|||
|
|||
public class SqlSugarConst |
|||
{ |
|||
/// <summary>
|
|||
/// 默认数据库标识
|
|||
/// </summary>
|
|||
public const string DefaultConfigId = "Cis"; |
|||
|
|||
/// <summary>
|
|||
/// 默认表主键
|
|||
/// </summary>
|
|||
public const string DefaultPrimaryKey = "Id"; |
|||
} |
@ -1,61 +0,0 @@ |
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 框架实体基类Id
|
|||
/// </summary>
|
|||
public abstract class EntityBaseId |
|||
{ |
|||
/// <summary>
|
|||
/// 雪花Id
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true, IsIdentity = false)] |
|||
public virtual long Id { get; set; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 框架实体基类
|
|||
/// </summary>
|
|||
public abstract class EntityBase : EntityBaseId |
|||
{ |
|||
/// <summary>
|
|||
/// 创建时间
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "创建时间")] |
|||
public virtual DateTime? CreateTime { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 更新时间
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "更新时间")] |
|||
public virtual DateTime? UpdateTime { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 创建者Id
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "创建者Id")] |
|||
public virtual long? CreateUserId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 修改者Id
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "修改者Id")] |
|||
public virtual long? UpdateUserId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 软删除
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "软删除")] |
|||
public virtual bool IsDelete { get; set; } = false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 业务数据实体基类(数据权限)
|
|||
/// </summary>
|
|||
public abstract class DataEntityBase : EntityBase |
|||
{ |
|||
/// <summary>
|
|||
/// 创建者部门Id
|
|||
/// </summary>
|
|||
[SugarColumn(ColumnDescription = "创建者部门Id")] |
|||
public virtual long? CreateOrgId { get; set; } |
|||
} |
@ -1,47 +0,0 @@ |
|||
namespace Cis.Core; |
|||
|
|||
public class Pagination |
|||
{ |
|||
/// <summary>
|
|||
/// 当前页
|
|||
/// </summary>
|
|||
public virtual int Index { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 页码容量
|
|||
/// </summary>
|
|||
public virtual int Size { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 排序字段
|
|||
/// </summary>
|
|||
public virtual string Field { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 排序方向
|
|||
/// </summary>
|
|||
public virtual string Order { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 总记录数
|
|||
/// </summary>
|
|||
public virtual int Total { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 页码数
|
|||
/// </summary>
|
|||
public virtual int Number |
|||
{ |
|||
get |
|||
{ |
|||
if (Size > 0) |
|||
{ |
|||
return ((Size % Total) == 0) ? (Size / Total) : ((Size / Total) + 1); |
|||
} |
|||
else |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1,128 +0,0 @@ |
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 全局规范化结果
|
|||
/// </summary>
|
|||
[UnifyModel(typeof(RespParam<>))] |
|||
public class RespParamProvider : IUnifyResultProvider |
|||
{ |
|||
/// <summary>
|
|||
/// 异常返回值
|
|||
/// </summary>
|
|||
/// <param name="context"></param>
|
|||
/// <param name="metadata"></param>
|
|||
/// <returns></returns>
|
|||
public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata) |
|||
{ |
|||
return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 成功返回值
|
|||
/// </summary>
|
|||
/// <param name="context"></param>
|
|||
/// <param name="data"></param>
|
|||
/// <returns></returns>
|
|||
public IActionResult OnSucceeded(ActionExecutedContext context, object data) |
|||
{ |
|||
return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 验证失败返回值
|
|||
/// </summary>
|
|||
/// <param name="context"></param>
|
|||
/// <param name="metadata"></param>
|
|||
/// <returns></returns>
|
|||
public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata) |
|||
{ |
|||
return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.ValidationResult)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 特定状态码返回值
|
|||
/// </summary>
|
|||
/// <param name="context"></param>
|
|||
/// <param name="statusCode"></param>
|
|||
/// <param name="unifyResultSettings"></param>
|
|||
/// <returns></returns>
|
|||
public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings) |
|||
{ |
|||
// 设置响应状态码
|
|||
UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings); |
|||
|
|||
switch (statusCode) |
|||
{ |
|||
// 处理 401 状态码
|
|||
case StatusCodes.Status401Unauthorized: |
|||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "401 登录已过期,请重新登录"), |
|||
App.GetOptions<JsonOptions>()?.JsonSerializerOptions); |
|||
break; |
|||
// 处理 403 状态码
|
|||
case StatusCodes.Status403Forbidden: |
|||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "403 禁止访问,没有权限"), |
|||
App.GetOptions<JsonOptions>()?.JsonSerializerOptions); |
|||
break; |
|||
|
|||
default: break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 返回 RESTful 风格结果集
|
|||
/// </summary>
|
|||
/// <param name="statusCode"></param>
|
|||
/// <param name="succeeded"></param>
|
|||
/// <param name="data"></param>
|
|||
/// <param name="errors"></param>
|
|||
/// <returns></returns>
|
|||
private static RespParam<object> RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default) |
|||
{ |
|||
return new RespParam<object> |
|||
{ |
|||
Code = statusCode, |
|||
Message = errors is string str ? str : JSON.Serialize(errors), |
|||
Data = data, |
|||
Type = succeeded ? "success" : "error", |
|||
Extras = UnifyContext.Take(), |
|||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() |
|||
}; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 全局返回结果
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
public class RespParam<T> |
|||
{ |
|||
/// <summary>
|
|||
/// 状态码
|
|||
/// </summary>
|
|||
public int Code { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 类型success、warning、error
|
|||
/// </summary>
|
|||
public string Type { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 错误信息
|
|||
/// </summary>
|
|||
public string Message { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 数据
|
|||
/// </summary>
|
|||
public T Data { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 附加数据
|
|||
/// </summary>
|
|||
public object Extras { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 时间戳
|
|||
/// </summary>
|
|||
public long Timestamp { get; set; } |
|||
} |
@ -1,19 +0,0 @@ |
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 通用状态枚举
|
|||
/// </summary>
|
|||
public enum StatusEnum |
|||
{ |
|||
/// <summary>
|
|||
/// 停用
|
|||
/// </summary>
|
|||
[Description("停用")] |
|||
Disable = 0, |
|||
|
|||
/// <summary>
|
|||
/// 启用
|
|||
/// </summary>
|
|||
[Description("启用")] |
|||
Enable = 1, |
|||
} |
@ -1,151 +0,0 @@ |
|||
using Newtonsoft.Json.Linq; |
|||
using System.Text; |
|||
|
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 对象拓展
|
|||
/// </summary>
|
|||
[SuppressSniffer] |
|||
public static class ObjectExtension |
|||
{ |
|||
/// <summary>
|
|||
/// 判断类型是否实现某个泛型
|
|||
/// </summary>
|
|||
/// <param name="type">类型</param>
|
|||
/// <param name="generic">泛型类型</param>
|
|||
/// <returns>bool</returns>
|
|||
public static bool HasImplementedRawGeneric(this Type type, Type generic) |
|||
{ |
|||
// 检查接口类型
|
|||
var isTheRawGenericType = type.GetInterfaces().Any(IsTheRawGenericType); |
|||
if (isTheRawGenericType) return true; |
|||
|
|||
// 检查类型
|
|||
while (type != null && type != typeof(object)) |
|||
{ |
|||
isTheRawGenericType = IsTheRawGenericType(type); |
|||
if (isTheRawGenericType) return true; |
|||
type = type.BaseType; |
|||
} |
|||
|
|||
return false; |
|||
|
|||
// 判断逻辑
|
|||
bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将字典转化为QueryString格式
|
|||
/// </summary>
|
|||
/// <param name="dict"></param>
|
|||
/// <param name="urlEncode"></param>
|
|||
/// <returns></returns>
|
|||
public static string ToQueryString(this Dictionary<string, string> dict, bool urlEncode = true) |
|||
{ |
|||
return string.Join("&", dict.Select(p => $"{(urlEncode ? p.Key?.UrlEncode() : "")}={(urlEncode ? p.Value?.UrlEncode() : "")}")); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将字符串URL编码
|
|||
/// </summary>
|
|||
/// <param name="str"></param>
|
|||
/// <returns></returns>
|
|||
public static string UrlEncode(this string str) |
|||
{ |
|||
return string.IsNullOrEmpty(str) ? "" : System.Web.HttpUtility.UrlEncode(str, Encoding.UTF8); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// List转DataTable
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
/// <param name="list"></param>
|
|||
/// <returns></returns>
|
|||
public static DataTable ToDataTable<T>(this List<T> list) |
|||
{ |
|||
DataTable result = new(); |
|||
if (list.Count > 0) |
|||
{ |
|||
// result.TableName = list[0].GetType().Name; // 表名赋值
|
|||
PropertyInfo[] propertys = list[0].GetType().GetProperties(); |
|||
foreach (PropertyInfo pi in propertys) |
|||
{ |
|||
Type colType = pi.PropertyType; |
|||
if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>)) |
|||
{ |
|||
colType = colType.GetGenericArguments()[0]; |
|||
} |
|||
if (IsIgnoreColumn(pi)) |
|||
continue; |
|||
result.Columns.Add(pi.Name, colType); |
|||
} |
|||
for (int i = 0; i < list.Count; i++) |
|||
{ |
|||
ArrayList tempList = new(); |
|||
foreach (PropertyInfo pi in propertys) |
|||
{ |
|||
if (IsIgnoreColumn(pi)) |
|||
continue; |
|||
object obj = pi.GetValue(list[i], null); |
|||
tempList.Add(obj); |
|||
} |
|||
object[] array = tempList.ToArray(); |
|||
result.LoadDataRow(array, true); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 对象序列化成Json字符串
|
|||
/// </summary>
|
|||
/// <param name="obj"></param>
|
|||
/// <returns></returns>
|
|||
public static string ToJson(this object obj) |
|||
{ |
|||
return JSON.GetJsonSerializer().Serialize(obj); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Json字符串反序列化成对象
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
/// <param name="json"></param>
|
|||
/// <returns></returns>
|
|||
public static T ToObject<T>(this string json) |
|||
{ |
|||
return JSON.GetJsonSerializer().Deserialize<T>(json); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 字串反序列化成linq对象
|
|||
/// </summary>
|
|||
/// <param name="Json">字串</param>
|
|||
/// <returns></returns>
|
|||
public static JObject ToJObject(this string Json) |
|||
{ |
|||
return string.IsNullOrEmpty(Json) ? JObject.Parse("{}") : JObject.Parse(Json.Replace(" ", "")); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 排除SqlSugar忽略的列
|
|||
/// </summary>
|
|||
/// <param name="pi"></param>
|
|||
/// <returns></returns>
|
|||
private static bool IsIgnoreColumn(PropertyInfo pi) |
|||
{ |
|||
var sc = pi.GetCustomAttributes<SugarColumn>(false).FirstOrDefault(u => u.IsIgnore == true); |
|||
return sc != null; |
|||
} |
|||
|
|||
public static bool ExistsKey(this IDictionary dict, object key) |
|||
{ |
|||
foreach (object item in dict.Keys) |
|||
{ |
|||
if (Equals(key, item)) |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
} |
@ -1,17 +0,0 @@ |
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 缓存配置选项
|
|||
/// </summary>
|
|||
public sealed class CacheOptions : IConfigurableOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 缓存类型
|
|||
/// </summary>
|
|||
public string CacheType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Redis连接字符串
|
|||
/// </summary>
|
|||
public string RedisConnectionString { get; set; } |
|||
} |
@ -1,20 +0,0 @@ |
|||
namespace Cis.Core; |
|||
|
|||
/// <summary>
|
|||
/// 数据库配置选项
|
|||
/// </summary>
|
|||
public sealed class DbConnectionOptions : IConfigurableOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 数据库集合
|
|||
/// </summary>
|
|||
public List<DbConnectionConfig> ConnectionConfigs { get; set; } |
|||
} |
|||
|
|||
public sealed class DbConnectionConfig : ConnectionConfig |
|||
{ |
|||
/// <summary>
|
|||
/// 启用库表初始化
|
|||
/// </summary>
|
|||
public bool EnableInitDb { get; set; } |
|||
} |
@ -0,0 +1,12 @@ |
|||
{ |
|||
"version": 1, |
|||
"isRoot": true, |
|||
"tools": { |
|||
"dotnet-ef": { |
|||
"version": "7.0.0", |
|||
"commands": [ |
|||
"dotnet-ef" |
|||
] |
|||
} |
|||
} |
|||
} |
@ -1,124 +0,0 @@ |
|||
using EC.Helper.CameraSDK; |
|||
|
|||
namespace EC.Helper.Test; |
|||
|
|||
public class CameraSDKTest |
|||
{ |
|||
public static void Test() |
|||
{ |
|||
//HiKTest();
|
|||
//DaHuaTest();
|
|||
//YuShiTest();
|
|||
} |
|||
|
|||
public static void HiKTest() |
|||
{ |
|||
Console.WriteLine("====HiK=========================="); |
|||
|
|||
try |
|||
{ |
|||
string ip = "192.168.1.65"; |
|||
string username = "admin"; |
|||
string password = "hk123456"; |
|||
int type = (int)CameraType.HiK; |
|||
CameraInfo cameraInfo = CameraInfo.New(type, ip, username, password); |
|||
ICameraSDK sdk = new HiKSDK(cameraInfo); |
|||
ICameraSDK sdk2 = new HiKSDK(cameraInfo); |
|||
sdk.Init(); |
|||
sdk2.Init(); |
|||
|
|||
PtzInfo ptzInfo = sdk.GetPtzInfo(); |
|||
Console.WriteLine($"{ptzInfo.Pan}, {ptzInfo.Tilt}, {ptzInfo.Zoom}"); |
|||
|
|||
sdk.Destory(); |
|||
|
|||
Thread.Sleep(1000); |
|||
|
|||
ptzInfo = sdk.GetPtzInfo(); |
|||
Console.WriteLine($"{ptzInfo.Pan}, {ptzInfo.Tilt}, {ptzInfo.Zoom}"); |
|||
|
|||
ptzInfo = sdk2.GetPtzInfo(); |
|||
Console.WriteLine($"{ptzInfo.Pan}, {ptzInfo.Tilt}, {ptzInfo.Zoom}"); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine(e.ToString()); |
|||
throw; |
|||
} |
|||
Console.WriteLine("================================="); |
|||
} |
|||
|
|||
public static void DaHuaTest() |
|||
{ |
|||
Console.WriteLine("====DaHua========================"); |
|||
|
|||
try |
|||
{ |
|||
string ip = "192.168.1.71"; |
|||
string username = "admin"; |
|||
string password = "hk123456"; |
|||
int type = (int)CameraType.DaHua; |
|||
CameraInfo cameraInfo = CameraInfo.New(type, ip, username, password); |
|||
ICameraSDK sdk = new DaHuaSDK(cameraInfo); |
|||
ICameraSDK sdk2 = new DaHuaSDK(cameraInfo); |
|||
sdk.Init(); |
|||
sdk2.Init(); |
|||
PtzInfo ptzInfo = sdk.GetPtzInfo(); |
|||
Console.WriteLine($"{ptzInfo.Pan}, {ptzInfo.Tilt}, {ptzInfo.Zoom}"); |
|||
|
|||
sdk.Destory(); |
|||
|
|||
Thread.Sleep(1000); |
|||
|
|||
ptzInfo = sdk.GetPtzInfo(); |
|||
Console.WriteLine($"{ptzInfo.Pan}, {ptzInfo.Tilt}, {ptzInfo.Zoom}"); |
|||
|
|||
ptzInfo = sdk2.GetPtzInfo(); |
|||
Console.WriteLine($"{ptzInfo.Pan}, {ptzInfo.Tilt}, {ptzInfo.Zoom}"); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine(e.ToString()); |
|||
throw; |
|||
} |
|||
|
|||
Console.WriteLine("================================="); |
|||
} |
|||
|
|||
public static void YuShiTest() |
|||
{ |
|||
Console.WriteLine("====YuShi========================"); |
|||
|
|||
try |
|||
{ |
|||
string ip = "192.168.1.109"; |
|||
string username = "admin"; |
|||
string password = "hk123456"; |
|||
int type = (int)CameraType.YuShi; |
|||
CameraInfo cameraInfo = CameraInfo.New(type, ip, username, password); |
|||
ICameraSDK sdk = new YuShiSDK(cameraInfo); |
|||
ICameraSDK sdk2 = new YuShiSDK(cameraInfo); |
|||
sdk.Init(); |
|||
sdk2.Init(); |
|||
PtzInfo ptzInfo = sdk.GetPtzInfo(); |
|||
Console.WriteLine($"{ptzInfo.Pan}, {ptzInfo.Tilt}, {ptzInfo.Zoom}"); |
|||
|
|||
sdk.Destory(); |
|||
|
|||
Thread.Sleep(1000); |
|||
|
|||
ptzInfo = sdk.GetPtzInfo(); |
|||
Console.WriteLine($"{ptzInfo.Pan}, {ptzInfo.Tilt}, {ptzInfo.Zoom}"); |
|||
|
|||
ptzInfo = sdk2.GetPtzInfo(); |
|||
Console.WriteLine($"{ptzInfo.Pan}, {ptzInfo.Tilt}, {ptzInfo.Zoom}"); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine(e.ToString()); |
|||
throw; |
|||
} |
|||
|
|||
Console.WriteLine("================================="); |
|||
} |
|||
} |
Loading…
Reference in new issue