Compare commits
10 Commits
69bae8feca
...
dc0cbc4903
| Author | SHA1 | Date | |
|---|---|---|---|
| dc0cbc4903 | |||
| 9129878d80 | |||
| 51aa552a0c | |||
| fda19d315e | |||
| 434fc0f9cd | |||
| e4bd3583c5 | |||
| d71a206975 | |||
| 41c3ec8f93 | |||
| 3c9ac046f7 | |||
| 014f5ca8bf |
@ -26,6 +26,10 @@ In der Übersicht nur Daten aus der Datenbank, vorerst keine Daten aus Wetris.
|
|||||||
2) der Zeitpunkt des letzten Reports innerhalb der Zone
|
2) der Zeitpunkt des letzten Reports innerhalb der Zone
|
||||||
3) keine Referenz sondern Kopie der Position/Timestamp?
|
3) keine Referenz sondern Kopie der Position/Timestamp?
|
||||||
- Alarm / Zuordnung sind unabhängig von der Gruppe. Die Gruppe / der Zulauf ergibt sich erst in der Webanwendung. (Ist das sinnvoll?)
|
- Alarm / Zuordnung sind unabhängig von der Gruppe. Die Gruppe / der Zulauf ergibt sich erst in der Webanwendung. (Ist das sinnvoll?)
|
||||||
|
- Für die Abfrage in der Gruppe der Zonen Außenweser/Binnenweser: Jeweils "neuester" Alarm:
|
||||||
|
- wenn kein weiterer Alarm: o : unspezifisch
|
||||||
|
- wenn vorh. Alarm in Zone mit niedrigere Seq : <- eingehend
|
||||||
|
- wenn vorh. Alarm in Zone mit höherer Seq : -> abgehend
|
||||||
|
|
||||||
## Stand Dez 22
|
## Stand Dez 22
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@ namespace bsmd.AIS2Service
|
|||||||
private static Timer _staleTargetTimer; // cleanup sitrep
|
private static Timer _staleTargetTimer; // cleanup sitrep
|
||||||
private static Timer _stalePosReportTimer; // clean db
|
private static Timer _stalePosReportTimer; // clean db
|
||||||
private static IDisposable _restAPISelfHost = null;
|
private static IDisposable _restAPISelfHost = null;
|
||||||
|
private static AIS_SQLiteStorage _sqliteStorage = null;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -38,16 +39,17 @@ namespace bsmd.AIS2Service
|
|||||||
_tasks.Add(new SerialTCPReader(Properties.Settings.Default.DataSourceHost, Properties.Settings.Default.DataSourcePort, _inputLines));
|
_tasks.Add(new SerialTCPReader(Properties.Settings.Default.DataSourceHost, Properties.Settings.Default.DataSourcePort, _inputLines));
|
||||||
_tasks.Add(new AISDecoder(_inputLines, _decodedClasses));
|
_tasks.Add(new AISDecoder(_inputLines, _decodedClasses));
|
||||||
_tasks.Add(new SitRep(_decodedClasses, _sitRepList, _dbSaveTargets));
|
_tasks.Add(new SitRep(_decodedClasses, _sitRepList, _dbSaveTargets));
|
||||||
AIS_SQLiteStorage sqliteStorage = new AIS_SQLiteStorage(_dbSaveTargets);
|
_sqliteStorage = new AIS_SQLiteStorage(_dbSaveTargets);
|
||||||
_tasks.Add(sqliteStorage);
|
_tasks.Add(_sqliteStorage);
|
||||||
_tasks.Add(new AISZoneMonitor(_sitRepList, sqliteStorage));
|
_tasks.Add(new AISZoneMonitor(_sitRepList, _sqliteStorage));
|
||||||
|
|
||||||
// preload sit rep
|
// preload sit rep
|
||||||
Dictionary<int, AIS_Target> targets = await sqliteStorage.LoadTargets();
|
Dictionary<int, AIS_Target> targets = await _sqliteStorage.LoadTargets();
|
||||||
foreach(int key in targets.Keys)
|
foreach(int key in targets.Keys)
|
||||||
{
|
{
|
||||||
_sitRepList.TryAdd(key, targets[key]);
|
_sitRepList.TryAdd(key, targets[key]);
|
||||||
}
|
}
|
||||||
|
_log.InfoFormat("preloaded {0} targets", _sitRepList.Count);
|
||||||
|
|
||||||
foreach (var task in _tasks)
|
foreach (var task in _tasks)
|
||||||
{
|
{
|
||||||
@ -58,7 +60,7 @@ namespace bsmd.AIS2Service
|
|||||||
|
|
||||||
// init timer tasks
|
// init timer tasks
|
||||||
_staleTargetTimer = new Timer(StaleTargetTimerCheck, null, 0, 60000); // check every minute, start immediately
|
_staleTargetTimer = new Timer(StaleTargetTimerCheck, null, 0, 60000); // check every minute, start immediately
|
||||||
_stalePosReportTimer = new Timer(StalePosReportCheck, sqliteStorage, 0, 60000 * 10); // every ten minutes,
|
_stalePosReportTimer = new Timer(StalePosReportCheck, _sqliteStorage, 0, 60000 * 10); // every ten minutes,
|
||||||
|
|
||||||
// if required start self-hosted owin endpoint
|
// if required start self-hosted owin endpoint
|
||||||
if(Properties.Settings.Default.EnableRestAPIEndpoint)
|
if(Properties.Settings.Default.EnableRestAPIEndpoint)
|
||||||
@ -87,10 +89,9 @@ namespace bsmd.AIS2Service
|
|||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
public static ConcurrentDictionary<int, AIS_Target> SitRep
|
public static ConcurrentDictionary<int, AIS_Target> SitRep { get { return _sitRepList; } }
|
||||||
{
|
|
||||||
get { return _sitRepList; }
|
public static AIS_SQLiteStorage SQLiteStorage { get { return _sqliteStorage; } }
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@ -21,8 +21,8 @@ namespace bsmd.AIS2Service
|
|||||||
|
|
||||||
#region Fields
|
#region Fields
|
||||||
|
|
||||||
ConcurrentDictionary<int, AIS_Target> _sitRepDict;
|
private readonly ConcurrentDictionary<int, AIS_Target> _sitRepDict;
|
||||||
AIS_SQLiteStorage _storage;
|
private readonly AIS_SQLiteStorage _storage;
|
||||||
private Thread _thread;
|
private Thread _thread;
|
||||||
private bool _stopFlag = false;
|
private bool _stopFlag = false;
|
||||||
private static readonly ILog _log = LogManager.GetLogger(typeof(AISZoneMonitor));
|
private static readonly ILog _log = LogManager.GetLogger(typeof(AISZoneMonitor));
|
||||||
@ -45,18 +45,24 @@ namespace bsmd.AIS2Service
|
|||||||
{
|
{
|
||||||
// load zones from storage
|
// load zones from storage
|
||||||
List<MonitorZone> allZones = _storage.LoadMonitorZones();
|
List<MonitorZone> allZones = _storage.LoadMonitorZones();
|
||||||
Dictionary<int, List<MonitorZone>> testZones = new Dictionary<int, List<MonitorZone>>();
|
Dictionary<int, List<AlarmAssignmentZone>> testDict = new Dictionary<int, List<AlarmAssignmentZone>>();
|
||||||
|
// letzte Alarme
|
||||||
|
// Dictionary<int, Alarm> currentAlarms = _storage.LoadAlarms();
|
||||||
|
|
||||||
foreach(MonitorZone zone in allZones)
|
foreach(MonitorZone zone in allZones)
|
||||||
{
|
{
|
||||||
// load up Zone list for all assignment mmsi's (to check)
|
// load up Zone list for all assignment mmsi's (to check)
|
||||||
foreach(MonitorAssignment ma in zone.Assignments)
|
foreach(MonitorAssignment ma in zone.Assignments)
|
||||||
{
|
{
|
||||||
if(!testZones.ContainsKey(ma.MMSI)) testZones[ma.MMSI] = new List<MonitorZone>();
|
if(!testDict.ContainsKey(ma.MMSI)) testDict[ma.MMSI] = new List<AlarmAssignmentZone>();
|
||||||
testZones[ma.MMSI].Add(zone);
|
ma.Alarms.AddRange(_storage.LoadAlarms(ma));
|
||||||
|
AlarmAssignmentZone aazone = new AlarmAssignmentZone();
|
||||||
|
aazone.Zone = zone;
|
||||||
|
aazone.Assignment = ma;
|
||||||
|
testDict[ma.MMSI].Add(aazone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// loop
|
// loop
|
||||||
while(!_stopFlag)
|
while(!_stopFlag)
|
||||||
{
|
{
|
||||||
@ -64,13 +70,41 @@ namespace bsmd.AIS2Service
|
|||||||
|
|
||||||
foreach(int mmsi in _sitRepDict.Keys)
|
foreach(int mmsi in _sitRepDict.Keys)
|
||||||
{
|
{
|
||||||
if(testZones.ContainsKey(mmsi))
|
if(testDict.ContainsKey(mmsi))
|
||||||
{
|
{
|
||||||
|
if (!_sitRepDict.ContainsKey(mmsi)) continue;
|
||||||
AIS_Target target = _sitRepDict[mmsi];
|
AIS_Target target = _sitRepDict[mmsi];
|
||||||
foreach(MonitorZone zone in testZones[mmsi]) {
|
foreach(AlarmAssignmentZone aazone in testDict[mmsi]) {
|
||||||
if(zone.IsPointInPolygon4(target.Position))
|
if(aazone.Zone.IsPointInPolygon4(target.Position))
|
||||||
{
|
{
|
||||||
_log.InfoFormat("{0} is in zone {1}", target.ToString(), zone.Name);
|
_log.DebugFormat("{0} is in zone {1}", target.ToString(), aazone.Zone.Name);
|
||||||
|
Alarm alarm;
|
||||||
|
if (aazone.Assignment.Alarms.Count > 0)
|
||||||
|
{
|
||||||
|
alarm = aazone.Assignment.Alarms[0];
|
||||||
|
if(alarm.Timestamp_Last.HasValue &&
|
||||||
|
((DateTime.Now - alarm.Timestamp_Last.Value).TotalMinutes > Properties.Settings.Default.MinAlarmIntervalMins))
|
||||||
|
{
|
||||||
|
// this "old" alarm will be triggered again (overwritten)
|
||||||
|
alarm.Acknowledged = null;
|
||||||
|
alarm.Timestamp_Last = null;
|
||||||
|
alarm.Timestamp_First = DateTime.Now;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// update the last time the target registered in the zone
|
||||||
|
alarm.Timestamp_Last = DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// create new alarm
|
||||||
|
alarm = new Alarm(-1, aazone.Assignment);
|
||||||
|
alarm.Timestamp_First = DateTime.Now;
|
||||||
|
alarm.ZoneMonitorType = aazone.Assignment.MonitorType;
|
||||||
|
aazone.Assignment.Alarms.Add(alarm);
|
||||||
|
}
|
||||||
|
_storage.Save(alarm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,6 +147,15 @@ namespace bsmd.AIS2Service
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region class AssignmentZoneAlarm composition
|
||||||
|
|
||||||
|
private class AlarmAssignmentZone
|
||||||
|
{
|
||||||
|
public MonitorZone Zone { get; set; }
|
||||||
|
public MonitorAssignment Assignment { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
using System;
|
using log4net;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using log4net;
|
using System.Threading;
|
||||||
using System.Collections.Concurrent;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace bsmd.AIS2Service
|
namespace bsmd.AIS2Service
|
||||||
{
|
{
|
||||||
@ -16,13 +15,14 @@ namespace bsmd.AIS2Service
|
|||||||
/// past track. It is just intended to function as a "saving the state" of the AIS situation.
|
/// past track. It is just intended to function as a "saving the state" of the AIS situation.
|
||||||
/// Attention: Alarm zones / alarms are also stored here. This might or might not be such a great idea.
|
/// Attention: Alarm zones / alarms are also stored here. This might or might not be such a great idea.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AIS_SQLiteStorage : IAISThread
|
public class AIS_SQLiteStorage : IAISThread, IDisposable
|
||||||
{
|
{
|
||||||
#region Fields
|
#region Fields
|
||||||
|
|
||||||
private readonly SQLiteConnection _connection;
|
private readonly SQLiteConnection _connection;
|
||||||
private Thread _thread;
|
private Thread _thread;
|
||||||
private bool _stopFlag = false;
|
private bool _stopFlag = false;
|
||||||
|
private bool disposedValue;
|
||||||
private static readonly ILog _log = LogManager.GetLogger(typeof(AIS_SQLiteStorage));
|
private static readonly ILog _log = LogManager.GetLogger(typeof(AIS_SQLiteStorage));
|
||||||
private readonly ConcurrentQueue<AIS_Target> _inputQueue;
|
private readonly ConcurrentQueue<AIS_Target> _inputQueue;
|
||||||
private readonly Dictionary<int, AIS_Target> _storageTargets = new Dictionary<int, AIS_Target>();
|
private readonly Dictionary<int, AIS_Target> _storageTargets = new Dictionary<int, AIS_Target>();
|
||||||
@ -54,7 +54,7 @@ namespace bsmd.AIS2Service
|
|||||||
/// monitor zone loader func for the zone alarm watchdog (doesn't need groups or such)
|
/// monitor zone loader func for the zone alarm watchdog (doesn't need groups or such)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public List<MonitorZone> LoadMonitorZones()
|
public List<MonitorZone> LoadMonitorZones(bool loadInnerCollections = true)
|
||||||
{
|
{
|
||||||
List<MonitorZone> monitorZones = new List<MonitorZone>();
|
List<MonitorZone> monitorZones = new List<MonitorZone>();
|
||||||
if ((_connection == null) || (_connection.State != ConnectionState.Open)) return monitorZones; // can't load but return nothing in a friendly way
|
if ((_connection == null) || (_connection.State != ConnectionState.Open)) return monitorZones; // can't load but return nothing in a friendly way
|
||||||
@ -79,51 +79,54 @@ namespace bsmd.AIS2Service
|
|||||||
reader.Close();
|
reader.Close();
|
||||||
lzCmd.Dispose();
|
lzCmd.Dispose();
|
||||||
|
|
||||||
// load vertices for each zone
|
if (loadInnerCollections)
|
||||||
string loadVertexString = "SELECT Id, latitude, longitude FROM zone_vertex WHERE monitor_zone_id = @ID";
|
|
||||||
SQLiteCommand lvCmd = new SQLiteCommand(loadVertexString, _connection);
|
|
||||||
foreach(MonitorZone mz in monitorZones)
|
|
||||||
{
|
{
|
||||||
lvCmd.Parameters.Clear();
|
// load vertices for each zone
|
||||||
lvCmd.Parameters.AddWithValue("@ID", mz.Id);
|
string loadVertexString = "SELECT Id, latitude, longitude FROM zone_vertex WHERE monitor_zone_id = @ID";
|
||||||
reader = lvCmd.ExecuteReader();
|
SQLiteCommand lvCmd = new SQLiteCommand(loadVertexString, _connection);
|
||||||
if(reader.HasRows)
|
foreach (MonitorZone mz in monitorZones)
|
||||||
{
|
{
|
||||||
while(reader.Read())
|
lvCmd.Parameters.Clear();
|
||||||
|
lvCmd.Parameters.AddWithValue("@ID", mz.Id);
|
||||||
|
reader = lvCmd.ExecuteReader();
|
||||||
|
if (reader.HasRows)
|
||||||
{
|
{
|
||||||
int id = reader.GetInt32(0);
|
while (reader.Read())
|
||||||
GeoPoint gp = new GeoPoint(id);
|
{
|
||||||
gp.Lat = reader.GetDouble(1);
|
int id = reader.GetInt32(0);
|
||||||
gp.Lon = reader.GetDouble(2);
|
GeoPoint gp = new GeoPoint(id);
|
||||||
mz.Vertices.Add(gp);
|
gp.Lat = reader.GetDouble(1);
|
||||||
|
gp.Lon = reader.GetDouble(2);
|
||||||
|
mz.Vertices.Add(gp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
reader.Close();
|
||||||
}
|
}
|
||||||
reader.Close();
|
lvCmd.Dispose();
|
||||||
}
|
|
||||||
lvCmd.Dispose();
|
|
||||||
|
|
||||||
// load mmsi / zone assignments for each zone
|
// load mmsi / zone assignments for each zone
|
||||||
string loadAssignmentsString = "SELECT id, mmsi, type FROM zone_assignment WHERE monitor_zone_id = @ID";
|
string loadAssignmentsString = "SELECT id, mmsi, type FROM zone_assignment WHERE monitor_zone_id = @ID";
|
||||||
SQLiteCommand laCmd = new SQLiteCommand(loadAssignmentsString, _connection);
|
SQLiteCommand laCmd = new SQLiteCommand(loadAssignmentsString, _connection);
|
||||||
foreach (MonitorZone mz in monitorZones)
|
foreach (MonitorZone mz in monitorZones)
|
||||||
{
|
|
||||||
laCmd.Parameters.Clear();
|
|
||||||
laCmd.Parameters.AddWithValue("@ID", mz.Id);
|
|
||||||
reader = laCmd.ExecuteReader();
|
|
||||||
if (reader.HasRows)
|
|
||||||
{
|
{
|
||||||
while (reader.Read())
|
laCmd.Parameters.Clear();
|
||||||
|
laCmd.Parameters.AddWithValue("@ID", mz.Id);
|
||||||
|
reader = laCmd.ExecuteReader();
|
||||||
|
if (reader.HasRows)
|
||||||
{
|
{
|
||||||
int id = reader.GetInt32(0);
|
while (reader.Read())
|
||||||
MonitorAssignment ma = new MonitorAssignment(id);
|
{
|
||||||
ma.MMSI = reader.GetInt32(1);
|
int id = reader.GetInt32(0);
|
||||||
ma.MonitorType = (MonitorAssignment.ZoneMonitorType)reader.GetInt32(2);
|
MonitorAssignment ma = new MonitorAssignment(id);
|
||||||
mz.Assignments.Add(ma);
|
ma.MMSI = reader.GetInt32(1);
|
||||||
|
ma.MonitorType = (MonitorAssignment.ZoneMonitorType)reader.GetInt32(2);
|
||||||
|
mz.Assignments.Add(ma);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
reader.Close();
|
||||||
}
|
}
|
||||||
reader.Close();
|
laCmd.Dispose();
|
||||||
}
|
}
|
||||||
laCmd.Dispose();
|
|
||||||
|
|
||||||
return monitorZones;
|
return monitorZones;
|
||||||
}
|
}
|
||||||
@ -180,7 +183,8 @@ namespace bsmd.AIS2Service
|
|||||||
int id = reader.GetInt32(0);
|
int id = reader.GetInt32(0);
|
||||||
Alarm alarm = new Alarm(id, assignment);
|
Alarm alarm = new Alarm(id, assignment);
|
||||||
alarm.Timestamp_First = reader.GetDateTime(1);
|
alarm.Timestamp_First = reader.GetDateTime(1);
|
||||||
alarm.Timestamp_Last = reader.GetDateTime(2);
|
if(!reader.IsDBNull(2))
|
||||||
|
alarm.Timestamp_Last = reader.GetDateTime(2);
|
||||||
alarm.ZoneMonitorType = (MonitorAssignment.ZoneMonitorType)reader.GetInt32(3);
|
alarm.ZoneMonitorType = (MonitorAssignment.ZoneMonitorType)reader.GetInt32(3);
|
||||||
if(!reader.IsDBNull(4))
|
if(!reader.IsDBNull(4))
|
||||||
alarm.Acknowledged = reader.GetDateTime(4);
|
alarm.Acknowledged = reader.GetDateTime(4);
|
||||||
@ -207,8 +211,10 @@ namespace bsmd.AIS2Service
|
|||||||
if (alarm.Id <= 0)
|
if (alarm.Id <= 0)
|
||||||
{
|
{
|
||||||
// insert
|
// insert
|
||||||
string saveAlarmString = $"INSERT INTO alarm (zone_assignment_id, timestamp_first, timestamp_last, type, acknowledged) VALUES ({alarm.Assignment.Id}, '{alarm.Timestamp_First}', '{alarm.Timestamp_Last}', {alarm.ZoneMonitorType}, {alarm.Acknowledged})";
|
string saveAlarmString = $"INSERT INTO alarm (zone_assignment_id, timestamp_first, timestamp_last, type) VALUES ({alarm.Assignment.Id}, @TS_FIRST, @TS_LAST, {(int) alarm.ZoneMonitorType})";
|
||||||
SQLiteCommand cmd = new SQLiteCommand(saveAlarmString, _connection);
|
SQLiteCommand cmd = new SQLiteCommand(saveAlarmString, _connection);
|
||||||
|
cmd.Parameters.AddWithValue("@TS_FIRST", alarm.Timestamp_First);
|
||||||
|
cmd.Parameters.AddWithValue("@TS_LAST", alarm.Timestamp_Last);
|
||||||
int insertedRows = cmd.ExecuteNonQuery();
|
int insertedRows = cmd.ExecuteNonQuery();
|
||||||
cmd.Dispose();
|
cmd.Dispose();
|
||||||
alarm.Id = GetLastInsertId();
|
alarm.Id = GetLastInsertId();
|
||||||
@ -217,8 +223,14 @@ namespace bsmd.AIS2Service
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// update
|
// update
|
||||||
string updateAlarmString = $"UPDATE alarm SET acknowledged = {alarm.Acknowledged}, timestamp_last = '{alarm.Timestamp_Last}' WHERE id = {alarm.Id}";
|
string updateAlarmString = $"UPDATE alarm SET acknowledged = @TS_ACK, timestamp_first = @TS_FIRST, timestamp_last = @TS_LAST WHERE id = {alarm.Id}";
|
||||||
SQLiteCommand cmd = new SQLiteCommand(updateAlarmString, _connection);
|
SQLiteCommand cmd = new SQLiteCommand(updateAlarmString, _connection);
|
||||||
|
cmd.Parameters.AddWithValue("@TS_FIRST", alarm.Timestamp_First);
|
||||||
|
cmd.Parameters.AddWithValue("@TS_LAST", alarm.Timestamp_Last);
|
||||||
|
if(alarm.Acknowledged.HasValue)
|
||||||
|
cmd.Parameters.AddWithValue("@TS_ACK", alarm.Acknowledged);
|
||||||
|
else
|
||||||
|
cmd.Parameters.AddWithValue("@TS_ACK", DBNull.Value);
|
||||||
int updatedRows = cmd.ExecuteNonQuery();
|
int updatedRows = cmd.ExecuteNonQuery();
|
||||||
cmd.Dispose();
|
cmd.Dispose();
|
||||||
return (updatedRows == 1);
|
return (updatedRows == 1);
|
||||||
@ -247,6 +259,8 @@ namespace bsmd.AIS2Service
|
|||||||
groups.Add(mGroup);
|
groups.Add(mGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reader.Close();
|
||||||
|
laCmd.Dispose();
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,6 +387,42 @@ namespace bsmd.AIS2Service
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region ShipLocationReport
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method to allow the web interface to load all ship alarms in a group
|
||||||
|
/// (this makes sense since zones appear als columns in the overview)
|
||||||
|
/// </summary>
|
||||||
|
public List<ShipLocationReport> GetShipLocationReports(long groupId)
|
||||||
|
{
|
||||||
|
List<ShipLocationReport> slrs = new List<ShipLocationReport>();
|
||||||
|
|
||||||
|
string loadSLRString = "SELECT a.timestamp_first, a.timestamp_last, za.mmsi, mz.id FROM alarm a " +
|
||||||
|
"INNER JOIN zone_assignment za ON a.zone_assignment_id = za.id " +
|
||||||
|
"INNER JOIN monitor_zone mz ON za.monitor_zone_id = mz.id " +
|
||||||
|
$"WHERE mz.monitor_group_id = {groupId} ORDER BY mz.sequence";
|
||||||
|
|
||||||
|
SQLiteCommand laCmd = new SQLiteCommand(loadSLRString, _connection);
|
||||||
|
SQLiteDataReader reader = laCmd.ExecuteReader();
|
||||||
|
if (reader.HasRows)
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
ShipLocationReport slr = new ShipLocationReport();
|
||||||
|
slr.Timestamp_First = reader.GetDateTime(0);
|
||||||
|
if (reader.IsDBNull(1)) continue; // we dont want very new alarms
|
||||||
|
slr.Timestamp_Last = reader.GetDateTime(1);
|
||||||
|
slr.MMSI = reader.GetInt32(2);
|
||||||
|
slr.MonitorZoneId = reader.GetInt64(3);
|
||||||
|
slrs.Add(slr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.Close();
|
||||||
|
return slrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region private methods
|
#region private methods
|
||||||
@ -620,5 +670,32 @@ namespace bsmd.AIS2Service
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable implementation
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if(_connection.State == ConnectionState.Open)
|
||||||
|
{
|
||||||
|
_connection.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@
|
|||||||
<value>60</value>
|
<value>60</value>
|
||||||
</setting>
|
</setting>
|
||||||
<setting name="SQLiteDBConnectionString" serializeAs="String">
|
<setting name="SQLiteDBConnectionString" serializeAs="String">
|
||||||
<value>Data Source=ais_initial.db;Version=3;</value>
|
<value>Data Source=ais_initial.db;Version=3;Synchronous=OFF;Journal Mode=MEMORY;</value>
|
||||||
</setting>
|
</setting>
|
||||||
<setting name="PosReportDBCleanupDays" serializeAs="String">
|
<setting name="PosReportDBCleanupDays" serializeAs="String">
|
||||||
<value>7</value>
|
<value>7</value>
|
||||||
@ -67,6 +67,12 @@
|
|||||||
<setting name="MonitorTargetSaturationSecs" serializeAs="String">
|
<setting name="MonitorTargetSaturationSecs" serializeAs="String">
|
||||||
<value>120</value>
|
<value>120</value>
|
||||||
</setting>
|
</setting>
|
||||||
|
<setting name="MinAlarmIntervalMins" serializeAs="String">
|
||||||
|
<value>60</value>
|
||||||
|
</setting>
|
||||||
|
<setting name="AutoAlarmExpiryHours" serializeAs="String">
|
||||||
|
<value>24</value>
|
||||||
|
</setting>
|
||||||
</bsmd.AIS2Service.Properties.Settings>
|
</bsmd.AIS2Service.Properties.Settings>
|
||||||
</applicationSettings>
|
</applicationSettings>
|
||||||
<runtime>
|
<runtime>
|
||||||
|
|||||||
@ -1,311 +0,0 @@
|
|||||||
// Copyright (c) 2022 - schick Informatik
|
|
||||||
// bsmd.AIS2Service [MonitorZone.cs]: %UserDisplayName%
|
|
||||||
// Description: Represents a geographical area that should be checked against
|
|
||||||
// ship positions
|
|
||||||
|
|
||||||
using log4net;
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace bsmd.AIS2Service
|
|
||||||
{
|
|
||||||
|
|
||||||
#region DBEntity
|
|
||||||
|
|
||||||
public class DBEntity
|
|
||||||
{
|
|
||||||
protected long _id; // PK from database
|
|
||||||
|
|
||||||
public DBEntity(long id)
|
|
||||||
{
|
|
||||||
_id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long Id { get { return _id; } set { _id = value; } }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region class MonitorGroup
|
|
||||||
|
|
||||||
public class MonitorGroup : DBEntity
|
|
||||||
{
|
|
||||||
|
|
||||||
private static readonly ILog _log = LogManager.GetLogger(typeof(MonitorGroup));
|
|
||||||
|
|
||||||
#region fields
|
|
||||||
|
|
||||||
private string _name;
|
|
||||||
private readonly List<MonitorZone> _zones = new List<MonitorZone>();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Construction
|
|
||||||
|
|
||||||
public MonitorGroup(long id, string name) : base( id )
|
|
||||||
{
|
|
||||||
_name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
public List<MonitorZone> Zones { get { return _zones; } }
|
|
||||||
|
|
||||||
public string Name { get { return _name; } set { _name = value; } }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region public static methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Da Basti nun eine Datei mit allen Elementen exportiert gibt es eine einzelne Funktion, die alles
|
|
||||||
/// importiert
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filename"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static List<MonitorGroup> LoadGroups(string filename)
|
|
||||||
{
|
|
||||||
List<MonitorGroup> groups = new List<MonitorGroup>();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (File.Exists(filename))
|
|
||||||
{
|
|
||||||
XDocument kml = XDocument.Load(filename);
|
|
||||||
XNamespace ns = "http://www.opengis.net/kml/2.2";
|
|
||||||
|
|
||||||
foreach(XElement rootFolderNode in kml.Root.Element(ns + "Document").Element(ns + "Folder").Elements(ns + "Folder"))
|
|
||||||
{
|
|
||||||
MonitorGroup mg = new MonitorGroup(-1, rootFolderNode.Element(ns + "name").Value);
|
|
||||||
int sequence = 1;
|
|
||||||
foreach(XElement placemark in rootFolderNode.Elements(ns + "Placemark"))
|
|
||||||
{
|
|
||||||
MonitorZone mz = new MonitorZone(-1, placemark.Element(ns + "name").Value);
|
|
||||||
mz.Active = true;
|
|
||||||
mz.Sequence = sequence;
|
|
||||||
// now add all vertices
|
|
||||||
string[] vertices = placemark.Element(ns + "Polygon").Element(ns + "outerBoundaryIs").Element(ns + "LinearRing").Element(ns + "coordinates").Value.Split(' ');
|
|
||||||
for (int i = 0; i < vertices.Length - 1; i++)
|
|
||||||
{
|
|
||||||
string[] pointElems = vertices[i].Trim().Split(',');
|
|
||||||
if (pointElems.Length != 3) continue;
|
|
||||||
GeoPoint gp = new GeoPoint(-1);
|
|
||||||
gp.Lon = Double.Parse(pointElems[0], System.Globalization.NumberFormatInfo.InvariantInfo);
|
|
||||||
gp.Lat = Double.Parse(pointElems[1], System.Globalization.NumberFormatInfo.InvariantInfo);
|
|
||||||
mz.Vertices.Add(gp);
|
|
||||||
}
|
|
||||||
|
|
||||||
mg.Zones.Add(mz);
|
|
||||||
sequence++;
|
|
||||||
}
|
|
||||||
|
|
||||||
groups.Add(mg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Exception ex)
|
|
||||||
{
|
|
||||||
_log.Error(ex.ToString());
|
|
||||||
}
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region public methods
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return this.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region class MonitorZone
|
|
||||||
|
|
||||||
public class MonitorZone : DBEntity, IComparable<MonitorZone>
|
|
||||||
{
|
|
||||||
|
|
||||||
#region fields
|
|
||||||
|
|
||||||
private readonly List<GeoPoint> _vertices = new List<GeoPoint>();
|
|
||||||
private readonly List<MonitorAssignment> _assignments = new List<MonitorAssignment>();
|
|
||||||
private readonly string _name;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Construction
|
|
||||||
|
|
||||||
public MonitorZone(long id, string name) : base (id)
|
|
||||||
{
|
|
||||||
_name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
public string Name { get { return _name; } }
|
|
||||||
|
|
||||||
public List<GeoPoint> Vertices { get { return _vertices; } }
|
|
||||||
|
|
||||||
public List<MonitorAssignment> Assignments { get { return _assignments; } }
|
|
||||||
|
|
||||||
public bool Active { get; set; }
|
|
||||||
|
|
||||||
public int Sequence { get; set; }
|
|
||||||
|
|
||||||
public long MonitorGroupId { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region public static methods
|
|
||||||
|
|
||||||
public static MonitorZone ImportFromKML(string filename)
|
|
||||||
{
|
|
||||||
MonitorZone result = null;
|
|
||||||
if (File.Exists(filename))
|
|
||||||
{
|
|
||||||
XDocument kml = XDocument.Load(filename);
|
|
||||||
XNamespace ns = "http://www.opengis.net/kml/2.2";
|
|
||||||
|
|
||||||
string name = kml.Root.Element(ns + "Document").Element(ns + "name").Value;
|
|
||||||
if (name.EndsWith(".kml")) name = name.Substring(0, name.Length - 4);
|
|
||||||
result = new MonitorZone(-1, name);
|
|
||||||
|
|
||||||
// now find all vertices
|
|
||||||
string[] vertices = kml.Root.Element(ns + "Document").Element(ns + "Placemark").Element(ns + "Polygon").Element(ns + "outerBoundaryIs").Element(ns + "LinearRing").Element(ns + "coordinates").Value.Split(' ');
|
|
||||||
for (int i = 0; i < vertices.Length - 1; i++)
|
|
||||||
{
|
|
||||||
string[] pointElems = vertices[i].Trim().Split(',');
|
|
||||||
if (pointElems.Length != 3) continue;
|
|
||||||
GeoPoint gp = new GeoPoint(-1);
|
|
||||||
gp.Lon = Double.Parse(pointElems[0], System.Globalization.NumberFormatInfo.InvariantInfo);
|
|
||||||
gp.Lat = Double.Parse(pointElems[1], System.Globalization.NumberFormatInfo.InvariantInfo);
|
|
||||||
result.Vertices.Add(gp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region public methods
|
|
||||||
|
|
||||||
public bool IsPointInPolygon4(GeoPoint testPoint)
|
|
||||||
{
|
|
||||||
bool result = false;
|
|
||||||
int j = _vertices.Count() - 1;
|
|
||||||
for (int i = 0; i < _vertices.Count(); i++)
|
|
||||||
{
|
|
||||||
if (_vertices[i].Lat < testPoint.Lat && _vertices[j].Lat >= testPoint.Lat || _vertices[j].Lat < testPoint.Lat && _vertices[i].Lat >= testPoint.Lat)
|
|
||||||
{
|
|
||||||
if (_vertices[i].Lon + (testPoint.Lat - _vertices[i].Lat) / (_vertices[j].Lat - _vertices[i].Lat) * (_vertices[j].Lon - _vertices[i].Lon) < testPoint.Lon)
|
|
||||||
{
|
|
||||||
result = !result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j = i;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CompareTo(MonitorZone other)
|
|
||||||
{
|
|
||||||
return this.Sequence.CompareTo(other.Sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return String.Format("{0} (Seq.:{1} #Vert.:{2}", this.Name, this.Sequence, this.Vertices.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region class GeoPoint
|
|
||||||
|
|
||||||
public class GeoPoint : DBEntity
|
|
||||||
{
|
|
||||||
|
|
||||||
public GeoPoint(long id) : base (id)
|
|
||||||
{}
|
|
||||||
|
|
||||||
public double Lat { get; set; }
|
|
||||||
public double Lon { get; set; }
|
|
||||||
|
|
||||||
public long MonitorZoneId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region class MonitorAssignment
|
|
||||||
|
|
||||||
public class MonitorAssignment : DBEntity
|
|
||||||
{
|
|
||||||
|
|
||||||
public MonitorAssignment(long id) : base(id)
|
|
||||||
{}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum ZoneMonitorType
|
|
||||||
{
|
|
||||||
INACTIVE = 0,
|
|
||||||
ENTER = 1,
|
|
||||||
EXIT = 2,
|
|
||||||
PASSTHROUGH = 4, // outside - enter - inside - exit - outside
|
|
||||||
LEAVE_AND_RETURN = 8 // inside - exit - outside - enter - inside
|
|
||||||
}
|
|
||||||
|
|
||||||
public int MMSI { get; set; }
|
|
||||||
|
|
||||||
public ZoneMonitorType MonitorType { get; set; } = ZoneMonitorType.INACTIVE;
|
|
||||||
|
|
||||||
public long MonitorZoneId { get; set; }
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return String.Format("{0} {1}", MMSI, MonitorType);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region class Alarm
|
|
||||||
|
|
||||||
public class Alarm : DBEntity
|
|
||||||
{
|
|
||||||
private readonly MonitorAssignment _assignment;
|
|
||||||
|
|
||||||
public Alarm(long id, MonitorAssignment assignment) : base(id) { _assignment = assignment; }
|
|
||||||
|
|
||||||
public MonitorAssignment Assignment { get { return _assignment; } }
|
|
||||||
|
|
||||||
public DateTime Timestamp_First { get; set; }
|
|
||||||
|
|
||||||
public DateTime Timestamp_Last { get; set; }
|
|
||||||
|
|
||||||
public DateTime? Acknowledged { get; set; }
|
|
||||||
|
|
||||||
public MonitorAssignment.ZoneMonitorType ZoneMonitorType { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
20
AIS/bsmd.AIS2Service/Properties/Settings.Designer.cs
generated
20
AIS/bsmd.AIS2Service/Properties/Settings.Designer.cs
generated
@ -70,7 +70,7 @@ namespace bsmd.AIS2Service.Properties {
|
|||||||
|
|
||||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Configuration.DefaultSettingValueAttribute("Data Source=ais_initial.db;Version=3;")]
|
[global::System.Configuration.DefaultSettingValueAttribute("Data Source=ais_initial.db;Version=3;Synchronous=OFF;Journal Mode=MEMORY;")]
|
||||||
public string SQLiteDBConnectionString {
|
public string SQLiteDBConnectionString {
|
||||||
get {
|
get {
|
||||||
return ((string)(this["SQLiteDBConnectionString"]));
|
return ((string)(this["SQLiteDBConnectionString"]));
|
||||||
@ -121,5 +121,23 @@ namespace bsmd.AIS2Service.Properties {
|
|||||||
return ((int)(this["MonitorTargetSaturationSecs"]));
|
return ((int)(this["MonitorTargetSaturationSecs"]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("60")]
|
||||||
|
public int MinAlarmIntervalMins {
|
||||||
|
get {
|
||||||
|
return ((int)(this["MinAlarmIntervalMins"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("24")]
|
||||||
|
public int AutoAlarmExpiryHours {
|
||||||
|
get {
|
||||||
|
return ((int)(this["AutoAlarmExpiryHours"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
<Value Profile="(Default)">60</Value>
|
<Value Profile="(Default)">60</Value>
|
||||||
</Setting>
|
</Setting>
|
||||||
<Setting Name="SQLiteDBConnectionString" Type="System.String" Scope="Application">
|
<Setting Name="SQLiteDBConnectionString" Type="System.String" Scope="Application">
|
||||||
<Value Profile="(Default)">Data Source=ais_initial.db;Version=3;</Value>
|
<Value Profile="(Default)">Data Source=ais_initial.db;Version=3;Synchronous=OFF;Journal Mode=MEMORY;</Value>
|
||||||
</Setting>
|
</Setting>
|
||||||
<Setting Name="PosReportDBCleanupDays" Type="System.Int32" Scope="Application">
|
<Setting Name="PosReportDBCleanupDays" Type="System.Int32" Scope="Application">
|
||||||
<Value Profile="(Default)">7</Value>
|
<Value Profile="(Default)">7</Value>
|
||||||
@ -35,5 +35,11 @@
|
|||||||
<Setting Name="MonitorTargetSaturationSecs" Type="System.Int32" Scope="Application">
|
<Setting Name="MonitorTargetSaturationSecs" Type="System.Int32" Scope="Application">
|
||||||
<Value Profile="(Default)">120</Value>
|
<Value Profile="(Default)">120</Value>
|
||||||
</Setting>
|
</Setting>
|
||||||
|
<Setting Name="MinAlarmIntervalMins" Type="System.Int32" Scope="Application">
|
||||||
|
<Value Profile="(Default)">60</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="AutoAlarmExpiryHours" Type="System.Int32" Scope="Application">
|
||||||
|
<Value Profile="(Default)">24</Value>
|
||||||
|
</Setting>
|
||||||
</Settings>
|
</Settings>
|
||||||
</SettingsFile>
|
</SettingsFile>
|
||||||
@ -94,25 +94,30 @@
|
|||||||
<DependentUpon>AIS2_Service.cs</DependentUpon>
|
<DependentUpon>AIS2_Service.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="AISClass.cs" />
|
<Compile Include="AISClass.cs" />
|
||||||
<Compile Include="AISController.cs" />
|
<Compile Include="webservice\AISController.cs" />
|
||||||
<Compile Include="AISDecoder.cs" />
|
<Compile Include="decoding\AISDecoder.cs" />
|
||||||
<Compile Include="AISManager.cs" />
|
<Compile Include="AISManager.cs" />
|
||||||
<Compile Include="AISZoneMonitor.cs" />
|
<Compile Include="AISZoneMonitor.cs" />
|
||||||
<Compile Include="AIS_BaseStation.cs" />
|
<Compile Include="decoding\AIS_BaseStation.cs" />
|
||||||
<Compile Include="AIS_BaseStationReport.cs" />
|
<Compile Include="decoding\AIS_BaseStationReport.cs" />
|
||||||
<Compile Include="AIS_ClassB.cs" />
|
<Compile Include="decoding\AIS_ClassB.cs" />
|
||||||
<Compile Include="AIS_ClassBExt.cs" />
|
<Compile Include="decoding\AIS_ClassBExt.cs" />
|
||||||
<Compile Include="AIS_ClassBStatic.cs" />
|
<Compile Include="decoding\AIS_ClassBStatic.cs" />
|
||||||
<Compile Include="AIS_PosReport.cs" />
|
<Compile Include="decoding\AIS_PosReport.cs" />
|
||||||
<Compile Include="AIS_SQLiteStorage.cs" />
|
<Compile Include="AIS_SQLiteStorage.cs" />
|
||||||
<Compile Include="AIS_StaticData.cs" />
|
<Compile Include="decoding\AIS_StaticData.cs" />
|
||||||
<Compile Include="AIS_Target.cs" />
|
<Compile Include="AIS_Target.cs" />
|
||||||
<Compile Include="IAISThread.cs" />
|
<Compile Include="IAISThread.cs" />
|
||||||
<Compile Include="Lookup.cs" />
|
<Compile Include="Lookup.cs" />
|
||||||
<Compile Include="MonitorZone.cs" />
|
<Compile Include="zone_alarm\Alarm.cs" />
|
||||||
<Compile Include="NMEA.cs" />
|
<Compile Include="zone_alarm\DBEntity.cs" />
|
||||||
<Compile Include="NMEA_AIS_Sentence.cs" />
|
<Compile Include="zone_alarm\GeoPoint.cs" />
|
||||||
<Compile Include="NMEA_PNMLS_Sentence.cs" />
|
<Compile Include="zone_alarm\MonitorAssignment.cs" />
|
||||||
|
<Compile Include="zone_alarm\MonitorGroup.cs" />
|
||||||
|
<Compile Include="zone_alarm\MonitorZone.cs" />
|
||||||
|
<Compile Include="decoding\NMEA.cs" />
|
||||||
|
<Compile Include="decoding\NMEA_AIS_Sentence.cs" />
|
||||||
|
<Compile Include="decoding\NMEA_PNMLS_Sentence.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="ProjectInstaller.cs">
|
<Compile Include="ProjectInstaller.cs">
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
@ -127,9 +132,12 @@
|
|||||||
<DependentUpon>Settings.settings</DependentUpon>
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="SerialTCPReader.cs" />
|
<Compile Include="SerialTCPReader.cs" />
|
||||||
|
<Compile Include="webservice\ShipLocationReport.cs" />
|
||||||
<Compile Include="SitRep.cs" />
|
<Compile Include="SitRep.cs" />
|
||||||
<Compile Include="StartupWebAPI.cs" />
|
<Compile Include="webservice\SLRController.cs" />
|
||||||
|
<Compile Include="webservice\StartupWebAPI.cs" />
|
||||||
<Compile Include="Util.cs" />
|
<Compile Include="Util.cs" />
|
||||||
|
<Compile Include="webservice\ZonesController.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\SQL\ais_initial.db">
|
<None Include="..\SQL\ais_initial.db">
|
||||||
|
|||||||
BIN
AIS/bsmd.AIS2Service/img/arrow_down_red.png
Normal file
BIN
AIS/bsmd.AIS2Service/img/arrow_down_red.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 933 B |
BIN
AIS/bsmd.AIS2Service/img/arrow_up_green.png
Normal file
BIN
AIS/bsmd.AIS2Service/img/arrow_up_green.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 950 B |
BIN
AIS/bsmd.AIS2Service/img/bullet_square_yellow.png
Normal file
BIN
AIS/bsmd.AIS2Service/img/bullet_square_yellow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 446 B |
BIN
AIS/bsmd.AIS2Service/img/clock.png
Normal file
BIN
AIS/bsmd.AIS2Service/img/clock.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
17
AIS/bsmd.AIS2Service/lauf.html
Normal file
17
AIS/bsmd.AIS2Service/lauf.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Zonen Übersicht</title>
|
||||||
|
<script type="text/javascript" src="zonen.js"></script>
|
||||||
|
</head>
|
||||||
|
<body onload="createAreas()">
|
||||||
|
<div class="table_group" id="root-div" />
|
||||||
|
<div id="bottomcenter">
|
||||||
|
(c) <a href="http://www.textbausteine.net" target="#">Informatikbüro Daniel Schick</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -6,123 +6,50 @@
|
|||||||
body{
|
body{
|
||||||
font-family: Helvetica;
|
font-family: Helvetica;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
background: rgba( 71, 147, 227, 1);
|
|
||||||
}
|
|
||||||
h2{
|
|
||||||
text-align: center;
|
|
||||||
font-size: 18px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
color: white;
|
|
||||||
padding: 30px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table Styles */
|
.styled-table {
|
||||||
|
|
||||||
.table-wrapper{
|
|
||||||
margin: 10px 70px 70px;
|
|
||||||
box-shadow: 0px 35px 50px rgba( 0, 0, 0, 0.2 );
|
|
||||||
}
|
|
||||||
|
|
||||||
.fl-table {
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: normal;
|
|
||||||
border: none;
|
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
margin: 25px 0;
|
||||||
max-width: 100%;
|
font-size: 0.9em;
|
||||||
white-space: nowrap;
|
font-family: sans-serif;
|
||||||
background-color: white;
|
min-width: 400px;
|
||||||
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fl-table td, .fl-table th {
|
.styled-table thead tr {
|
||||||
text-align: center;
|
background-color: #009879;
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fl-table td {
|
|
||||||
border-right: 1px solid #f8f8f8;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fl-table thead th {
|
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background: #4FC3A1;
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.styled-table th,
|
||||||
|
.styled-table td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.styled-table tbody tr {
|
||||||
|
border-bottom: 1px solid #dddddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.styled-table tbody tr:nth-of-type(even) {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.styled-table tbody tr:last-of-type {
|
||||||
|
border-bottom: 2px solid #009879;
|
||||||
|
}
|
||||||
|
|
||||||
|
.styled-table tbody tr.active-row {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #009879;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.fl-table thead th:nth-child(odd) {
|
#bottomcenter {
|
||||||
color: #ffffff;
|
position: fixed;
|
||||||
background: #324960;
|
left: 50%;
|
||||||
}
|
bottom: 20px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
.fl-table tr:nth-child(even) {
|
margin: 0 auto;
|
||||||
background: #F8F8F8;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.fl-table {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.table-wrapper:before{
|
|
||||||
content: "Scroll horizontally >";
|
|
||||||
display: block;
|
|
||||||
text-align: right;
|
|
||||||
font-size: 11px;
|
|
||||||
color: white;
|
|
||||||
padding: 0 0 10px;
|
|
||||||
}
|
|
||||||
.fl-table thead, .fl-table tbody, .fl-table thead th {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.fl-table thead th:last-child{
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
.fl-table thead {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.fl-table tbody {
|
|
||||||
width: auto;
|
|
||||||
position: relative;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
.fl-table td, .fl-table th {
|
|
||||||
padding: 20px .625em .625em .625em;
|
|
||||||
height: 60px;
|
|
||||||
vertical-align: middle;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
width: 120px;
|
|
||||||
font-size: 13px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
.fl-table thead th {
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #f7f7f9;
|
|
||||||
}
|
|
||||||
.fl-table tbody tr {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
.fl-table tbody tr:nth-child(odd) {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
.fl-table tr:nth-child(even) {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.fl-table tr td:nth-child(odd) {
|
|
||||||
background: #F8F8F8;
|
|
||||||
border-right: 1px solid #E6E4E4;
|
|
||||||
}
|
|
||||||
.fl-table tr td:nth-child(even) {
|
|
||||||
border-right: 1px solid #E6E4E4;
|
|
||||||
}
|
|
||||||
.fl-table tbody td {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
95
AIS/bsmd.AIS2Service/webservice/SLRController.cs
Normal file
95
AIS/bsmd.AIS2Service/webservice/SLRController.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Web.Http;
|
||||||
|
using log4net;
|
||||||
|
|
||||||
|
namespace bsmd.AIS2Service
|
||||||
|
{
|
||||||
|
public class SLRController : ApiController
|
||||||
|
{
|
||||||
|
private static readonly ILog _log = LogManager.GetLogger(typeof(SLRController));
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public IEnumerable<ShipLocationReport> Get([FromUri] int? id)
|
||||||
|
{
|
||||||
|
if (!id.HasValue) return null;
|
||||||
|
List<ShipLocationReport> result = AISManager.SQLiteStorage.GetShipLocationReports(id.Value);
|
||||||
|
|
||||||
|
// remove results not in SitRep
|
||||||
|
int obscureTargetCnt = result.RemoveAll(x => !AISManager.SitRep.ContainsKey(x.MMSI));
|
||||||
|
_log.InfoFormat("removed {0} obscure targets", obscureTargetCnt);
|
||||||
|
|
||||||
|
// remove targets w/o name (i.e. static data)
|
||||||
|
int unnamedPlayerCnt = result.RemoveAll(x => string.IsNullOrEmpty(AISManager.SitRep[x.MMSI].Name));
|
||||||
|
_log.InfoFormat("removed {0} unnamed targets", unnamedPlayerCnt);
|
||||||
|
|
||||||
|
// Class B targets entfernen
|
||||||
|
int classBReportCnt = result.RemoveAll(x => AISManager.SitRep[x.MMSI].IsClassB ?? false);
|
||||||
|
_log.InfoFormat("removed {0} class B alarms from list", classBReportCnt); // tut des?
|
||||||
|
|
||||||
|
// auch alles entfernen was "abgelaufen" ist und nicht im SitRep enthalten ist
|
||||||
|
int expiredCnt = result.RemoveAll(x => (DateTime.Now - x.Timestamp_Last).TotalMinutes > 1440);
|
||||||
|
_log.InfoFormat("removed {0} expired (> 1 day) alarms from list", expiredCnt);
|
||||||
|
|
||||||
|
Dictionary<int, List<ShipLocationReport>> mmsiDict = new Dictionary<int, List<ShipLocationReport>>();
|
||||||
|
foreach(ShipLocationReport report in result) {
|
||||||
|
if (!mmsiDict.ContainsKey(report.MMSI)) mmsiDict[report.MMSI] = new List<ShipLocationReport>();
|
||||||
|
mmsiDict[report.MMSI].Add(report);
|
||||||
|
if (AISManager.SitRep.ContainsKey(report.MMSI))
|
||||||
|
{
|
||||||
|
report.Destination = AISManager.SitRep[report.MMSI].Destination;
|
||||||
|
report.Name = AISManager.SitRep[report.MMSI].Name;
|
||||||
|
report.NavStatus = AIS_PosReport.GetNavStatus(AISManager.SitRep[report.MMSI].NavStatus);
|
||||||
|
report.IMO = AISManager.SitRep[report.MMSI].IMO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine "state" of vessel through alarm comparison. Possible values are:
|
||||||
|
// 0 = stationary (= equals)
|
||||||
|
// 1 = incoming
|
||||||
|
// 2 = outgoing
|
||||||
|
// 3 = expired
|
||||||
|
|
||||||
|
foreach(int key in mmsiDict.Keys)
|
||||||
|
{
|
||||||
|
bool expired = true;
|
||||||
|
DateTime? lastDate= null;
|
||||||
|
bool? incoming = null;
|
||||||
|
|
||||||
|
// first run through alarm list to determine state
|
||||||
|
foreach(ShipLocationReport slr in mmsiDict[key])
|
||||||
|
{
|
||||||
|
if((DateTime.Now - slr.Timestamp_Last).TotalHours < Properties.Settings.Default.AutoAlarmExpiryHours) expired = false;
|
||||||
|
if (lastDate == null)
|
||||||
|
{
|
||||||
|
lastDate = slr.Timestamp_Last;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
incoming = slr.Timestamp_Last < lastDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// second run through alarm list to set state flag in all entries
|
||||||
|
foreach (ShipLocationReport slr in mmsiDict[key])
|
||||||
|
{
|
||||||
|
if (expired)
|
||||||
|
{
|
||||||
|
slr.VoyageDirection = 3;
|
||||||
|
}
|
||||||
|
else if (incoming.HasValue)
|
||||||
|
{
|
||||||
|
if (incoming.Value) { slr.VoyageDirection = 1; }
|
||||||
|
else { slr.VoyageDirection = 2; }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slr.VoyageDirection = 0; // stationary / no comparison value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
AIS/bsmd.AIS2Service/webservice/ShipLocationReport.cs
Normal file
28
AIS/bsmd.AIS2Service/webservice/ShipLocationReport.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace bsmd.AIS2Service
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class to report a ship position / entry into a zone (aka alarm connected to zone)
|
||||||
|
/// </summary>
|
||||||
|
public class ShipLocationReport
|
||||||
|
{
|
||||||
|
public DateTime Timestamp_First { get; set; }
|
||||||
|
|
||||||
|
public DateTime Timestamp_Last { get; set; }
|
||||||
|
|
||||||
|
public long MonitorZoneId { get; set; }
|
||||||
|
|
||||||
|
public int MMSI { get; set; }
|
||||||
|
|
||||||
|
public int? IMO { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Destination { get; set; }
|
||||||
|
|
||||||
|
public string NavStatus { get; set; }
|
||||||
|
|
||||||
|
public int VoyageDirection { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,19 @@ namespace bsmd.AIS2Service
|
|||||||
routeTemplate: "api/{Controller}",
|
routeTemplate: "api/{Controller}",
|
||||||
defaults: new { id = RouteParameter.Optional, Controller = "AIS"}
|
defaults: new { id = RouteParameter.Optional, Controller = "AIS"}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
config.Routes.MapHttpRoute(
|
||||||
|
name: "ZonesList",
|
||||||
|
routeTemplate: "api/{Controller}",
|
||||||
|
defaults: new { id = RouteParameter.Optional, Controller = "Zones" }
|
||||||
|
);
|
||||||
|
|
||||||
|
config.Routes.MapHttpRoute(
|
||||||
|
name: "SLRList",
|
||||||
|
routeTemplate: "api/{Controller}",
|
||||||
|
defaults: new { id = RouteParameter.Optional, Controller = "SLR" }
|
||||||
|
);
|
||||||
|
|
||||||
config.EnableCors(cors);
|
config.EnableCors(cors);
|
||||||
appBuilder.UseWebApi(config);
|
appBuilder.UseWebApi(config);
|
||||||
|
|
||||||
41
AIS/bsmd.AIS2Service/webservice/ZonesController.cs
Normal file
41
AIS/bsmd.AIS2Service/webservice/ZonesController.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Web.Http;
|
||||||
|
using System.Web.Http.Cors;
|
||||||
|
|
||||||
|
namespace bsmd.AIS2Service
|
||||||
|
{
|
||||||
|
public class ZonesController : ApiController
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public IEnumerable<MonitorGroup> Get() // Get([FromUri] int? id)
|
||||||
|
{
|
||||||
|
List<MonitorZone> allZones = AISManager.SQLiteStorage.LoadMonitorZones(false);
|
||||||
|
List<MonitorGroup> groups = AISManager.SQLiteStorage.LoadGroups();
|
||||||
|
foreach(MonitorGroup group in groups)
|
||||||
|
{
|
||||||
|
foreach(MonitorZone zone in allZones)
|
||||||
|
{
|
||||||
|
if(group.Id == zone.MonitorGroupId)
|
||||||
|
{
|
||||||
|
group.Zones.Add(zone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.Zones.Sort();
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (!id.HasValue)
|
||||||
|
{
|
||||||
|
List<MonitorGroup> groups = AISManager.SQLiteStorage.LoadGroups();
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
AIS/bsmd.AIS2Service/zone_alarm/Alarm.cs
Normal file
30
AIS/bsmd.AIS2Service/zone_alarm/Alarm.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2023 - schick Informatik
|
||||||
|
// bsmd.AIS2Service [Alarm.cs]: Daniel Schick
|
||||||
|
// Description: alarm created if vessel interacts with zone
|
||||||
|
//
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace bsmd.AIS2Service
|
||||||
|
{
|
||||||
|
#region class Alarm
|
||||||
|
|
||||||
|
public class Alarm : DBEntity
|
||||||
|
{
|
||||||
|
private readonly MonitorAssignment _assignment;
|
||||||
|
|
||||||
|
public Alarm(long id, MonitorAssignment assignment) : base(id) { _assignment = assignment; }
|
||||||
|
|
||||||
|
public MonitorAssignment Assignment { get { return _assignment; } }
|
||||||
|
|
||||||
|
public DateTime Timestamp_First { get; set; }
|
||||||
|
|
||||||
|
public DateTime? Timestamp_Last { get; set; }
|
||||||
|
|
||||||
|
public DateTime? Acknowledged { get; set; }
|
||||||
|
|
||||||
|
public MonitorAssignment.ZoneMonitorType ZoneMonitorType { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
25
AIS/bsmd.AIS2Service/zone_alarm/DBEntity.cs
Normal file
25
AIS/bsmd.AIS2Service/zone_alarm/DBEntity.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) 2023 - schick Informatik
|
||||||
|
// bsmd.AIS2Service [DBEntity.cs]: Daniel Schick
|
||||||
|
// Description: Root class for SQlite database entities
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
namespace bsmd.AIS2Service
|
||||||
|
{
|
||||||
|
#region DBEntity
|
||||||
|
|
||||||
|
public class DBEntity
|
||||||
|
{
|
||||||
|
protected long _id; // PK from database
|
||||||
|
|
||||||
|
public DBEntity(long id)
|
||||||
|
{
|
||||||
|
_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long Id { get { return _id; } set { _id = value; } }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
23
AIS/bsmd.AIS2Service/zone_alarm/GeoPoint.cs
Normal file
23
AIS/bsmd.AIS2Service/zone_alarm/GeoPoint.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2023 - schick Informatik
|
||||||
|
// bsmd.AIS2Service.zone_alarm [GeoPoint.cs]: Daniel Schick
|
||||||
|
// Description: Single vertex of a zone
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace bsmd.AIS2Service
|
||||||
|
{
|
||||||
|
#region class GeoPoint
|
||||||
|
|
||||||
|
public class GeoPoint : DBEntity
|
||||||
|
{
|
||||||
|
|
||||||
|
public GeoPoint(long id) : base(id)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public double Lat { get; set; }
|
||||||
|
public double Lon { get; set; }
|
||||||
|
|
||||||
|
public long MonitorZoneId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
44
AIS/bsmd.AIS2Service/zone_alarm/MonitorAssignment.cs
Normal file
44
AIS/bsmd.AIS2Service/zone_alarm/MonitorAssignment.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2023 - schick Informatik
|
||||||
|
// bsmd.AIS2Service.zone_alarm [MonitorAssignment.cs]: Daniel Schick
|
||||||
|
// Description: Mapping between targets (MMSI) and zones
|
||||||
|
//
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace bsmd.AIS2Service
|
||||||
|
{
|
||||||
|
#region class MonitorAssignment
|
||||||
|
|
||||||
|
public class MonitorAssignment : DBEntity
|
||||||
|
{
|
||||||
|
|
||||||
|
public MonitorAssignment(long id) : base(id)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum ZoneMonitorType
|
||||||
|
{
|
||||||
|
INACTIVE = 0,
|
||||||
|
ENTER = 1,
|
||||||
|
EXIT = 2,
|
||||||
|
PASSTHROUGH = 4, // outside - enter - inside - exit - outside
|
||||||
|
LEAVE_AND_RETURN = 8 // inside - exit - outside - enter - inside
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MMSI { get; set; }
|
||||||
|
|
||||||
|
public ZoneMonitorType MonitorType { get; set; } = ZoneMonitorType.INACTIVE;
|
||||||
|
|
||||||
|
public long MonitorZoneId { get; set; }
|
||||||
|
|
||||||
|
public List<Alarm> Alarms { get; } = new List<Alarm>();
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return String.Format("{0} {1}", MMSI, MonitorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
112
AIS/bsmd.AIS2Service/zone_alarm/MonitorGroup.cs
Normal file
112
AIS/bsmd.AIS2Service/zone_alarm/MonitorGroup.cs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// Copyright (c) 2023 - schick Informatik
|
||||||
|
// bsmd.AIS2Service [MonitorGroup.cs]: Daniel Schick
|
||||||
|
// Description: Group container for zones
|
||||||
|
//
|
||||||
|
using log4net;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace bsmd.AIS2Service
|
||||||
|
{
|
||||||
|
#region class MonitorGroup
|
||||||
|
|
||||||
|
public class MonitorGroup : DBEntity
|
||||||
|
{
|
||||||
|
|
||||||
|
private static readonly ILog _log = LogManager.GetLogger(typeof(MonitorGroup));
|
||||||
|
|
||||||
|
#region fields
|
||||||
|
|
||||||
|
private string _name;
|
||||||
|
private readonly List<MonitorZone> _zones = new List<MonitorZone>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Construction
|
||||||
|
|
||||||
|
public MonitorGroup(long id, string name) : base(id)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
public List<MonitorZone> Zones { get { return _zones; } }
|
||||||
|
|
||||||
|
public string Name { get { return _name; } set { _name = value; } }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region public static methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Da Basti nun eine Datei mit allen Elementen exportiert gibt es eine einzelne Funktion, die alles
|
||||||
|
/// importiert
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static List<MonitorGroup> LoadGroups(string filename)
|
||||||
|
{
|
||||||
|
List<MonitorGroup> groups = new List<MonitorGroup>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(filename))
|
||||||
|
{
|
||||||
|
XDocument kml = XDocument.Load(filename);
|
||||||
|
XNamespace ns = "http://www.opengis.net/kml/2.2";
|
||||||
|
|
||||||
|
foreach (XElement rootFolderNode in kml.Root.Element(ns + "Document").Element(ns + "Folder").Elements(ns + "Folder"))
|
||||||
|
{
|
||||||
|
MonitorGroup mg = new MonitorGroup(-1, rootFolderNode.Element(ns + "name").Value);
|
||||||
|
int sequence = 1;
|
||||||
|
foreach (XElement placemark in rootFolderNode.Elements(ns + "Placemark"))
|
||||||
|
{
|
||||||
|
MonitorZone mz = new MonitorZone(-1, placemark.Element(ns + "name").Value);
|
||||||
|
mz.Active = true;
|
||||||
|
mz.Sequence = sequence;
|
||||||
|
// now add all vertices
|
||||||
|
string[] vertices = placemark.Element(ns + "Polygon").Element(ns + "outerBoundaryIs").Element(ns + "LinearRing").Element(ns + "coordinates").Value.Split(' ');
|
||||||
|
for (int i = 0; i < vertices.Length - 1; i++)
|
||||||
|
{
|
||||||
|
string[] pointElems = vertices[i].Trim().Split(',');
|
||||||
|
if (pointElems.Length != 3) continue;
|
||||||
|
GeoPoint gp = new GeoPoint(-1);
|
||||||
|
gp.Lon = Double.Parse(pointElems[0], System.Globalization.NumberFormatInfo.InvariantInfo);
|
||||||
|
gp.Lat = Double.Parse(pointElems[1], System.Globalization.NumberFormatInfo.InvariantInfo);
|
||||||
|
mz.Vertices.Add(gp);
|
||||||
|
}
|
||||||
|
|
||||||
|
mg.Zones.Add(mz);
|
||||||
|
sequence++;
|
||||||
|
}
|
||||||
|
|
||||||
|
groups.Add(mg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Error(ex.ToString());
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region public methods
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return this.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
121
AIS/bsmd.AIS2Service/zone_alarm/MonitorZone.cs
Normal file
121
AIS/bsmd.AIS2Service/zone_alarm/MonitorZone.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// Copyright (c) 2022 - schick Informatik
|
||||||
|
// bsmd.AIS2Service [MonitorZone.cs]: %UserDisplayName%
|
||||||
|
// Description: Represents a geographical area that should be checked against
|
||||||
|
// ship positions
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace bsmd.AIS2Service
|
||||||
|
{
|
||||||
|
|
||||||
|
#region class MonitorZone
|
||||||
|
|
||||||
|
public class MonitorZone : DBEntity, IComparable<MonitorZone>
|
||||||
|
{
|
||||||
|
|
||||||
|
#region fields
|
||||||
|
|
||||||
|
private readonly List<GeoPoint> _vertices = new List<GeoPoint>();
|
||||||
|
private readonly List<MonitorAssignment> _assignments = new List<MonitorAssignment>();
|
||||||
|
private readonly string _name;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Construction
|
||||||
|
|
||||||
|
public MonitorZone(long id, string name) : base (id)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
public string Name { get { return _name; } }
|
||||||
|
|
||||||
|
public List<GeoPoint> Vertices { get { return _vertices; } }
|
||||||
|
|
||||||
|
public List<MonitorAssignment> Assignments { get { return _assignments; } }
|
||||||
|
|
||||||
|
public bool Active { get; set; }
|
||||||
|
|
||||||
|
public int Sequence { get; set; }
|
||||||
|
|
||||||
|
public long MonitorGroupId { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region public static methods
|
||||||
|
|
||||||
|
public static MonitorZone ImportFromKML(string filename)
|
||||||
|
{
|
||||||
|
MonitorZone result = null;
|
||||||
|
if (File.Exists(filename))
|
||||||
|
{
|
||||||
|
XDocument kml = XDocument.Load(filename);
|
||||||
|
XNamespace ns = "http://www.opengis.net/kml/2.2";
|
||||||
|
|
||||||
|
string name = kml.Root.Element(ns + "Document").Element(ns + "name").Value;
|
||||||
|
if (name.EndsWith(".kml")) name = name.Substring(0, name.Length - 4);
|
||||||
|
result = new MonitorZone(-1, name);
|
||||||
|
|
||||||
|
// now find all vertices
|
||||||
|
string[] vertices = kml.Root.Element(ns + "Document").Element(ns + "Placemark").Element(ns + "Polygon").Element(ns + "outerBoundaryIs").Element(ns + "LinearRing").Element(ns + "coordinates").Value.Split(' ');
|
||||||
|
for (int i = 0; i < vertices.Length - 1; i++)
|
||||||
|
{
|
||||||
|
string[] pointElems = vertices[i].Trim().Split(',');
|
||||||
|
if (pointElems.Length != 3) continue;
|
||||||
|
GeoPoint gp = new GeoPoint(-1);
|
||||||
|
gp.Lon = Double.Parse(pointElems[0], System.Globalization.NumberFormatInfo.InvariantInfo);
|
||||||
|
gp.Lat = Double.Parse(pointElems[1], System.Globalization.NumberFormatInfo.InvariantInfo);
|
||||||
|
result.Vertices.Add(gp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region public methods
|
||||||
|
|
||||||
|
public bool IsPointInPolygon4(GeoPoint testPoint)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
int j = _vertices.Count() - 1;
|
||||||
|
for (int i = 0; i < _vertices.Count(); i++)
|
||||||
|
{
|
||||||
|
if (_vertices[i].Lat < testPoint.Lat && _vertices[j].Lat >= testPoint.Lat || _vertices[j].Lat < testPoint.Lat && _vertices[i].Lat >= testPoint.Lat)
|
||||||
|
{
|
||||||
|
if (_vertices[i].Lon + (testPoint.Lat - _vertices[i].Lat) / (_vertices[j].Lat - _vertices[i].Lat) * (_vertices[j].Lon - _vertices[i].Lon) < testPoint.Lon)
|
||||||
|
{
|
||||||
|
result = !result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j = i;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(MonitorZone other)
|
||||||
|
{
|
||||||
|
return this.Sequence.CompareTo(other.Sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return String.Format("{0} (Seq.:{1} #Vert.:{2}", this.Name, this.Sequence, this.Vertices.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
175
AIS/bsmd.AIS2Service/zonen.js
Normal file
175
AIS/bsmd.AIS2Service/zonen.js
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* startup, load groups from database */
|
||||||
|
|
||||||
|
var groupIds = [];
|
||||||
|
var groupZones = [];
|
||||||
|
|
||||||
|
async function loadAlarms(groupId)
|
||||||
|
{
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
const res = await fetch('http://localhost:9050/api/slr?id=' + groupId);
|
||||||
|
data = await res.json();
|
||||||
|
updateData(data, groupId);
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateData(data, groupId) {
|
||||||
|
|
||||||
|
var table = document.getElementById('table-' + groupId);
|
||||||
|
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
|
||||||
|
let row_id = "row_" + groupId + "_" + data[i].MMSI;
|
||||||
|
row = document.getElementById(row_id);
|
||||||
|
if(row == null) { // not found, create new row
|
||||||
|
row = document.createElement('tr');
|
||||||
|
row.setAttribute("id", row_id);
|
||||||
|
// add leading cells
|
||||||
|
var td1 = document.createElement('td');
|
||||||
|
//td1.innerHTML = data[i].Name;
|
||||||
|
row.appendChild(td1);
|
||||||
|
var td2 = document.createElement('td');
|
||||||
|
|
||||||
|
row.appendChild(td2);
|
||||||
|
var td3 = document.createElement('td');
|
||||||
|
// td3.innerHTML = data[i].IMO;
|
||||||
|
row.appendChild(td3);
|
||||||
|
// create dummy cells for each zone
|
||||||
|
for(var j = 0; j < groupZones[groupId].length; j++) {
|
||||||
|
var td = document.createElement('td');
|
||||||
|
td.id = "cell_" + groupId + "_" + groupZones[groupId][j] + "_" + data[i].MMSI;
|
||||||
|
row.appendChild(td);
|
||||||
|
}
|
||||||
|
// add trailing cells
|
||||||
|
var td4 = document.createElement('td');
|
||||||
|
//td4.innerHTML = data[i].NavStatus;
|
||||||
|
row.appendChild(td4);
|
||||||
|
var td5 = document.createElement('td');
|
||||||
|
//td5.innerHTML = data[i].MMSI;
|
||||||
|
row.appendChild(td5);
|
||||||
|
var td6 = document.createElement('td');
|
||||||
|
//td6.innerHTML = data[i].Destination;
|
||||||
|
row.appendChild(td6);
|
||||||
|
|
||||||
|
table.childNodes[1].appendChild(row); // append row to tbody subelement
|
||||||
|
}
|
||||||
|
row.setAttribute("isActive", "true");
|
||||||
|
const colCount = row.childNodes.length;
|
||||||
|
row.childNodes[0].innerHTML = data[i].Name;
|
||||||
|
if(data[i].VoyageDirection == 0) row.childNodes[1].innerHTML = '<img src="img/bullet_square_yellow.png" />';
|
||||||
|
if(data[i].VoyageDirection == 1) row.childNodes[1].innerHTML = '<img src="img/arrow_down_red.png" />';
|
||||||
|
if(data[i].VoyageDirection == 2) row.childNodes[1].innerHTML = '<img src="img/arrow_up_green.png" />';
|
||||||
|
if(data[i].VoyageDirection == 3) row.childNodes[1].innerHTML = '<img src="img/clock.png" />';
|
||||||
|
row.childNodes[2].innerHTML = data[i].IMO;
|
||||||
|
|
||||||
|
// find alarm cell and set value
|
||||||
|
let cellId = "cell_" + groupId + "_" + data[i].MonitorZoneId + "_" + data[i].MMSI;
|
||||||
|
var cell = document.getElementById(cellId);
|
||||||
|
if(cell != null) {
|
||||||
|
const timestamp = Date.parse(data[i].Timestamp_First);
|
||||||
|
const d = new Date(timestamp);
|
||||||
|
cell.innerHTML = ("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2) + " " + d.getDate() + "." + (d.getMonth() + 1) + "." + ("" + d.getFullYear()).slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
row.childNodes[colCount - 3].innerHTML = data[i].NavStatus;
|
||||||
|
row.childNodes[colCount - 2].innerHTML = data[i].MMSI;
|
||||||
|
row.childNodes[colCount - 1].innerHTML = data[i].Destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
// array in-place removal taking place
|
||||||
|
var i = table.childNodes[1].rows.length;
|
||||||
|
while (i--) {
|
||||||
|
row = table.childNodes[1].rows[i];
|
||||||
|
if(row["isActive"] !== "true") {
|
||||||
|
table.childNodes[1].rows.remove(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
|
||||||
|
for(var i = 0; i < groupIds.length; i++)
|
||||||
|
{
|
||||||
|
loadAlarms(groupIds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAreas()
|
||||||
|
{
|
||||||
|
fetch('http://localhost:9050/api/zones')
|
||||||
|
.then(function (response) {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function (data) {
|
||||||
|
createAreasFromData(data);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log('error: ' + err);
|
||||||
|
});
|
||||||
|
|
||||||
|
function createAreasFromData(data)
|
||||||
|
{
|
||||||
|
var root_div = document.getElementById('root-div');
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
groupIds.push(data[i].Id);
|
||||||
|
groupZones[data[i].Id] = [];
|
||||||
|
var aDiv = document.createElement('div');
|
||||||
|
aDiv.className = '';
|
||||||
|
aDiv.id = 'group-' + data[i].Id;
|
||||||
|
aDiv.innerHTML = data[i].Name;
|
||||||
|
root_div.appendChild(aDiv);
|
||||||
|
|
||||||
|
// create table header with zones
|
||||||
|
var aTable = document.createElement('table');
|
||||||
|
aTable.id = 'table-' + data[i].Id;
|
||||||
|
aTable.className = "styled-table";
|
||||||
|
var thead = document.createElement('thead');
|
||||||
|
aTable.appendChild(thead);
|
||||||
|
var tr = document.createElement('tr');
|
||||||
|
thead.appendChild(tr);
|
||||||
|
var th1 = document.createElement('th');
|
||||||
|
th1.innerHTML='Name';
|
||||||
|
tr.appendChild(th1);
|
||||||
|
var th2 = document.createElement('th');
|
||||||
|
th2.innerHTML='E/A';
|
||||||
|
tr.appendChild(th2);
|
||||||
|
var th3 = document.createElement('th');
|
||||||
|
th3.innerHTML='IMO';
|
||||||
|
tr.appendChild(th3);
|
||||||
|
|
||||||
|
for(var j = 0; j < data[i].Zones.length; j++) {
|
||||||
|
groupZones[data[i].Id].push(data[i].Zones[j].Id);
|
||||||
|
var aTH = document.createElement('th');
|
||||||
|
aTH.id = 'zone-' + data[i].Zones[j].Id;
|
||||||
|
aTH.innerHTML = data[i].Zones[j].Name;
|
||||||
|
tr.appendChild(aTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
var th4 = document.createElement('th');
|
||||||
|
th4.innerHTML='Nav. status';
|
||||||
|
tr.appendChild(th4);
|
||||||
|
var th5 = document.createElement('th');
|
||||||
|
th5.innerHTML='MMSI';
|
||||||
|
tr.appendChild(th5);
|
||||||
|
var th6 = document.createElement('th');
|
||||||
|
th6.innerHTML='Destination';
|
||||||
|
tr.appendChild(th6);
|
||||||
|
|
||||||
|
tr.setAttribute("isActive", "true"); // set marker so it won't get deleted later
|
||||||
|
|
||||||
|
aTable.appendChild(document.createElement('tbody'));
|
||||||
|
aDiv.appendChild(aTable);
|
||||||
|
aDiv.appendChild(document.createElement('hr'));
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(function () { update(); }, 15000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user