diff --git a/cmd/webclient/main.go b/cmd/webclient/main.go
index 05bc550..f07baaf 100644
--- a/cmd/webclient/main.go
+++ b/cmd/webclient/main.go
@@ -307,12 +307,11 @@ func (app *App) sendPing() {
}
func (app *App) ProcessEvent(msg *cot.Msg) {
-
switch {
case msg.GetType() == "t-x-c-t":
app.Logger.Debugf("ping from %s", msg.GetUid())
if c := app.GetContact(msg.GetUid()); c != nil {
- c.SetLastSeenNow(nil)
+ c.Update(nil)
}
case msg.GetType() == "t-x-c-t-r":
app.Logger.Debugf("pong")
@@ -328,17 +327,11 @@ func (app *App) ProcessEvent(msg *cot.Msg) {
app.Logger.Info("my own info")
break
}
- if c := app.GetContact(msg.GetUid()); c != nil {
- app.Logger.Infof("update pos %s (%s) %s", msg.GetUid(), msg.GetCallsign(), msg.GetType())
- c.SetLastSeenNow(msg)
- } else {
- app.Logger.Infof("new contact %s (%s) %s", msg.GetUid(), msg.GetCallsign(), msg.GetType())
- app.AddContact(msg.GetUid(), model.ContactFromEvent(msg))
- }
+ app.ProcessContact(msg)
} else {
- app.Logger.Infof("new unit %s (%s) %s", msg.GetUid(), msg.GetCallsign(), msg.GetType())
- app.AddUnit(msg.GetUid(), model.UnitFromEvent(msg))
+ app.ProcessUnit(msg)
}
+ return
case strings.HasPrefix(msg.GetType(), "b-"):
app.Logger.Infof("point %s (%s) %s", msg.GetUid(), msg.GetCallsign(), msg.GetType())
app.AddPoint(msg.GetUid(), model.PointFromEvent(msg))
@@ -347,11 +340,27 @@ func (app *App) ProcessEvent(msg *cot.Msg) {
}
}
-func (app *App) AddUnit(uid string, u *model.Unit) {
- if u == nil {
- return
+func (app *App) ProcessContact(msg *cot.Msg) {
+ if c := app.GetContact(msg.GetUid()); c != nil {
+ app.Logger.Infof("update contact %s (%s) %s", msg.GetUid(), msg.GetCallsign(), msg.GetType())
+ c.Update(msg)
+ } else {
+ app.Logger.Infof("new contact %s (%s) %s", msg.GetUid(), msg.GetCallsign(), msg.GetType())
+ if msg.GetUid() == app.uid {
+ return
+ }
+ app.units.Store(msg.GetUid(), model.ContactFromMsg(msg))
+ }
+}
+
+func (app *App) ProcessUnit(msg *cot.Msg) {
+ if u := app.GetUnit(msg.GetUid()); u != nil {
+ app.Logger.Infof("update unit %s (%s) %s", msg.GetUid(), msg.GetCallsign(), msg.GetType())
+ u.Update(msg)
+ } else {
+ app.Logger.Infof("new unit %s (%s) %s", msg.GetUid(), msg.GetCallsign(), msg.GetType())
+ app.units.Store(msg.GetUid(), model.UnitFromMsg(msg))
}
- app.units.Store(uid, u)
}
func (app *App) AddPoint(uid string, u *model.Point) {
@@ -361,17 +370,6 @@ func (app *App) AddPoint(uid string, u *model.Point) {
app.units.Store(uid, u)
}
-func (app *App) GetUnit(uid string) *model.Unit {
- if v, ok := app.units.Load(uid); ok {
- if unit, ok := v.(*model.Unit); ok {
- return unit
- } else {
- app.Logger.Errorf("invalid object for uid %s: %v", uid, v)
- }
- }
- return nil
-}
-
func (app *App) Remove(uid string) {
if _, ok := app.units.Load(uid); ok {
app.units.Delete(uid)
@@ -390,7 +388,18 @@ func (app *App) GetContact(uid string) *model.Contact {
if contact, ok := v.(*model.Contact); ok {
return contact
} else {
- app.Logger.Errorf("invalid object for uid %s: %v", uid, v)
+ app.Logger.Warnf("invalid object for uid %s: %s", uid, v)
+ }
+ }
+ return nil
+}
+
+func (app *App) GetUnit(uid string) *model.Unit {
+ if v, ok := app.units.Load(uid); ok {
+ if contact, ok := v.(*model.Unit); ok {
+ return contact
+ } else {
+ app.Logger.Warnf("invalid unit for uid %s: %s", uid, v)
}
}
return nil
@@ -399,13 +408,22 @@ func (app *App) GetContact(uid string) *model.Contact {
func (app *App) removeByLink(msg *cot.Msg) {
if msg.Detail != nil && msg.Detail.HasChild("link") {
uid := msg.Detail.GetFirstChild("link").GetAttr("uid")
+ typ := msg.Detail.GetFirstChild("link").GetAttr("type")
if uid == "" {
app.Logger.Errorf("invalid remove message: %s", msg.Detail)
return
}
- app.Logger.Debugf("remove %s by message", uid)
- if c := app.GetContact(uid); c != nil {
- c.SetOffline()
+ if v, ok := app.units.Load(uid); ok {
+ switch vv := v.(type) {
+ case *model.Contact:
+ app.Logger.Debugf("remove %s by message", uid)
+ vv.SetOffline()
+ return
+ case *model.Unit, *model.Point:
+ app.Logger.Debugf("remove unit/point %s type %s by message", uid, typ)
+ //app.units.Delete(uid)
+ return
+ }
}
}
}
diff --git a/cot/msg.go b/cot/msg.go
index c97b23e..964b631 100644
--- a/cot/msg.go
+++ b/cot/msg.go
@@ -83,6 +83,30 @@ func (m *Msg) PrintChat() string {
return fmt.Sprintf("%s -> %s: \"%s\"", from, to, text)
}
+func (m *Msg) GetLatLon() (float64, float64) {
+ if m == nil {
+ return 0, 0
+ }
+
+ return m.TakMessage.GetCotEvent().GetLat(), m.TakMessage.GetCotEvent().GetLon()
+}
+
+func (m *Msg) GetLat() float64 {
+ if m == nil {
+ return 0
+ }
+
+ return m.TakMessage.GetCotEvent().GetLat()
+}
+
+func (m *Msg) GetLon() float64 {
+ if m == nil {
+ return 0
+ }
+
+ return m.TakMessage.GetCotEvent().GetLon()
+}
+
func TimeFromMillis(ms uint64) time.Time {
return time.Unix(0, 1000000*int64(ms))
}
diff --git a/model/geo.go b/model/geo.go
new file mode 100644
index 0000000..cb1cde8
--- /dev/null
+++ b/model/geo.go
@@ -0,0 +1,26 @@
+package model
+
+import (
+ "math"
+)
+
+func DistBea(lat1, lon1, lat2, lon2 float64) (float64, float64) {
+ toRadian := math.Pi / 180
+ // haversine formula
+ // bearing
+ y := math.Sin((lon2-lon1)*toRadian) * math.Cos(lat2*toRadian)
+ x := math.Cos(lat1*toRadian)*math.Sin(lat2*toRadian) - math.Sin(lat1*toRadian)*math.Cos(lat2*toRadian)*math.Cos((lon2-lon1)*toRadian)
+ bea := math.Atan2(y, x) * 180 / math.Pi
+
+ if bea < 0 {
+ bea += 360
+ }
+ // distance
+ R := 6371000. // meters
+ deltaF := (lat2 - lat1) * toRadian
+ deltaL := (lon2 - lon1) * toRadian
+ a := math.Sin(deltaF/2)*math.Sin(deltaF/2) + math.Cos(lat1*toRadian)*math.Cos(lat2*toRadian)*math.Sin(deltaL/2)*math.Sin(deltaL/2)
+ c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
+ dist := R * c
+ return dist, bea
+}
diff --git a/model/http.go b/model/http.go
index 97356fb..4201375 100644
--- a/model/http.go
+++ b/model/http.go
@@ -43,27 +43,8 @@ func (c *Contact) ToWeb() *WebUnit {
c.mx.RLock()
defer c.mx.RUnlock()
- evt := c.msg.TakMessage.CotEvent
-
- w := &WebUnit{
- Uid: c.uid,
- Callsign: c.callsign,
- Category: "contact",
- Time: cot.TimeFromMillis(evt.SendTime),
- LastSeen: c.lastSeen,
- StaleTime: c.staleTime,
- StartTime: c.startTime,
- SendTime: c.sendTime,
- Type: c.type_,
- Lat: evt.Lat,
- Lon: evt.Lon,
- Hae: evt.Hae,
- Speed: evt.GetDetail().GetTrack().GetSpeed(),
- Course: evt.GetDetail().GetTrack().GetCourse(),
- Team: evt.GetDetail().GetGroup().GetName(),
- Role: evt.GetDetail().GetGroup().GetRole(),
- Sidc: getSIDC(c.type_),
- }
+ w := c.Item.ToWeb()
+ w.Category = "contact"
if c.online {
w.Status = "Online"
@@ -71,58 +52,49 @@ func (c *Contact) ToWeb() *WebUnit {
w.Status = "Offline"
}
- if v := evt.GetDetail().GetTakv(); v != nil {
+ if v := c.msg.TakMessage.GetCotEvent().GetDetail().GetTakv(); v != nil {
w.TakVersion = strings.Trim(fmt.Sprintf("%s %s on %s", v.Platform, v.Version, v.Device), " ")
}
- w.Text, _ = c.msg.Detail.GetChildValue("remarks")
return w
}
func (u *Unit) ToWeb() *WebUnit {
- evt := u.msg.TakMessage.CotEvent
-
- w := &WebUnit{
- Uid: u.uid,
- Callsign: u.callsign,
- Category: "unit",
- Time: cot.TimeFromMillis(evt.SendTime),
- LastSeen: u.received,
- StaleTime: u.staleTime,
- StartTime: u.startTime,
- SendTime: u.sendTime,
- Type: u.type_,
- Lat: evt.Lat,
- Lon: evt.Lon,
- Hae: evt.Hae,
- Speed: evt.GetDetail().GetTrack().GetSpeed(),
- Course: evt.GetDetail().GetTrack().GetCourse(),
- Team: evt.GetDetail().GetGroup().GetName(),
- Role: evt.GetDetail().GetGroup().GetRole(),
- Sidc: getSIDC(u.type_),
- }
- w.Text, _ = u.msg.Detail.GetChildValue("remarks")
+ w := u.Item.ToWeb()
+ w.Category = "unit"
return w
}
func (p *Point) ToWeb() *WebUnit {
- evt := p.msg.TakMessage.GetCotEvent()
+ w := p.Item.ToWeb()
+ w.Category = "point"
+
+ return w
+}
+
+func (i Item) ToWeb() *WebUnit {
+ evt := i.msg.TakMessage.CotEvent
w := &WebUnit{
- Uid: p.uid,
- Callsign: p.callsign,
- Category: "point",
- StaleTime: p.staleTime,
- StartTime: p.startTime,
- SendTime: p.sendTime,
- Type: p.type_,
+ Uid: i.uid,
+ Callsign: i.callsign,
+ Time: cot.TimeFromMillis(evt.SendTime),
+ LastSeen: i.received,
+ StaleTime: i.staleTime,
+ StartTime: i.startTime,
+ SendTime: i.sendTime,
+ Type: i.type_,
Lat: evt.Lat,
Lon: evt.Lon,
Hae: evt.Hae,
Speed: evt.GetDetail().GetTrack().GetSpeed(),
Course: evt.GetDetail().GetTrack().GetCourse(),
+ Team: evt.GetDetail().GetGroup().GetName(),
+ Role: evt.GetDetail().GetGroup().GetRole(),
+ Sidc: getSIDC(i.type_),
}
- w.Text, _ = p.msg.Detail.GetChildValue("remarks")
+
+ w.Text, _ = i.msg.Detail.GetChildValue("remarks")
return w
}
diff --git a/model/unit.go b/model/unit.go
index 4a744a8..edd0136 100644
--- a/model/unit.go
+++ b/model/unit.go
@@ -1,6 +1,7 @@
package model
import (
+ "fmt"
"sync"
"time"
@@ -11,7 +12,14 @@ const (
staleContactDelete = time.Minute * 30
)
-type Unit struct {
+type Pos struct {
+ time time.Time
+ lat float64
+ lon float64
+ speed float64
+}
+
+type Item struct {
uid string
type_ string
callsign string
@@ -22,32 +30,42 @@ type Unit struct {
msg *cot.Msg
}
+type Unit struct {
+ Item
+ mx sync.RWMutex
+ track []*Pos
+}
+
type Contact struct {
- uid string
- type_ string
- callsign string
- staleTime time.Time
- startTime time.Time
- sendTime time.Time
- lastSeen time.Time
- msg *cot.Msg
- online bool
- mx sync.RWMutex
+ Item
+ online bool
+ lastSeen time.Time
+ mx sync.RWMutex
+ track []*Pos
}
type Point struct {
- uid string
- type_ string
- callsign string
- staleTime time.Time
- startTime time.Time
- sendTime time.Time
- received time.Time
- msg *cot.Msg
+ Item
authorCallsign string
authorUid string
}
+func (c *Contact) String() string {
+ c.mx.RLock()
+ defer c.mx.RUnlock()
+ return fmt.Sprintf("contact: %s %s %s", c.uid, c.type_, c.callsign)
+}
+
+func (u *Unit) String() string {
+ u.mx.RLock()
+ defer u.mx.RUnlock()
+ return fmt.Sprintf("unit: %s %s %s", u.uid, u.type_, u.callsign)
+}
+
+func (p *Point) String() string {
+ return fmt.Sprintf("point: %s %s %s", p.uid, p.type_, p.callsign)
+}
+
func (u *Unit) GetMsg() *cot.Msg {
return u.msg
}
@@ -97,55 +115,98 @@ func (c *Contact) IsOnline() bool {
return c.online
}
-func ContactFromEvent(msg *cot.Msg) *Contact {
+func ContactFromMsg(msg *cot.Msg) *Contact {
+ pos := &Pos{
+ time: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetSendTime()),
+ lat: msg.TakMessage.GetCotEvent().GetLat(),
+ lon: msg.TakMessage.GetCotEvent().GetLon(),
+ speed: msg.TakMessage.GetCotEvent().GetDetail().GetTrack().GetSpeed(),
+ }
+
return &Contact{
- uid: msg.TakMessage.GetCotEvent().GetUid(),
- callsign: msg.TakMessage.GetCotEvent().GetDetail().GetContact().GetCallsign(),
- lastSeen: time.Now(),
- staleTime: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetStaleTime()),
- startTime: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetStartTime()),
- sendTime: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetSendTime()),
- type_: msg.TakMessage.GetCotEvent().GetType(),
- msg: msg,
- online: true,
- mx: sync.RWMutex{},
+ Item: ItemFromMsg(msg),
+ online: true,
+ lastSeen: time.Now(),
+ mx: sync.RWMutex{},
+ track: []*Pos{pos},
}
}
-func UnitFromEvent(msg *cot.Msg) *Unit {
+func UnitFromMsg(msg *cot.Msg) *Unit {
+ pos := &Pos{
+ time: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetSendTime()),
+ lat: msg.TakMessage.GetCotEvent().GetLat(),
+ lon: msg.TakMessage.GetCotEvent().GetLon(),
+ speed: msg.TakMessage.GetCotEvent().GetDetail().GetTrack().GetSpeed(),
+ }
+
return &Unit{
- uid: msg.TakMessage.GetCotEvent().GetUid(),
- callsign: msg.TakMessage.GetCotEvent().GetDetail().GetContact().GetCallsign(),
- staleTime: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetStaleTime()),
- startTime: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetStartTime()),
- sendTime: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetSendTime()),
- type_: msg.TakMessage.GetCotEvent().GetType(),
- msg: msg,
- received: time.Now(),
+ Item: ItemFromMsg(msg),
+ mx: sync.RWMutex{},
+ track: []*Pos{pos},
}
}
func PointFromEvent(msg *cot.Msg) *Point {
return &Point{
+ Item: ItemFromMsg(msg),
+ }
+}
+
+func ItemFromMsg(msg *cot.Msg) Item {
+ return Item{
uid: msg.TakMessage.GetCotEvent().GetUid(),
+ type_: msg.TakMessage.GetCotEvent().GetType(),
callsign: msg.TakMessage.GetCotEvent().GetDetail().GetContact().GetCallsign(),
staleTime: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetStaleTime()),
startTime: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetStartTime()),
sendTime: cot.TimeFromMillis(msg.TakMessage.GetCotEvent().GetSendTime()),
- type_: msg.TakMessage.GetCotEvent().GetType(),
msg: msg,
received: time.Now(),
}
}
-func (c *Contact) SetLastSeenNow(msg *cot.Msg) {
+func (i *Item) GetLanLon() (float64, float64) {
+ return i.msg.TakMessage.GetCotEvent().GetLat(), i.msg.TakMessage.GetCotEvent().GetLon()
+}
+
+func (c *Contact) Update(msg *cot.Msg) {
c.mx.Lock()
defer c.mx.Unlock()
c.online = true
c.lastSeen = time.Now()
if msg != nil {
+ pos := getPos(c.msg, msg)
c.msg = msg
+
+ if pos != nil {
+ c.track = append(c.track, pos)
+
+ if len(c.track) > 5000 {
+ c.track = c.track[len(c.track)-5000:]
+ }
+ }
+ }
+}
+
+func (u *Unit) Update(msg *cot.Msg) {
+ u.mx.Lock()
+ defer u.mx.Unlock()
+
+ u.Item = ItemFromMsg(msg)
+
+ if msg != nil {
+ pos := getPos(u.msg, msg)
+ u.msg = msg
+
+ if pos != nil {
+ u.track = append(u.track, pos)
+
+ if len(u.track) > 5000 {
+ u.track = u.track[len(u.track)-5000:]
+ }
+ }
}
}
@@ -155,3 +216,34 @@ func (c *Contact) SetOffline() {
c.online = false
}
+
+func getPos(msg1, msg2 *cot.Msg) *Pos {
+ if msg1 == nil || msg2 == nil {
+ return nil
+ }
+
+ lat1, lon1 := msg1.GetLatLon()
+
+ if lat1 == 0 && lon1 == 0 {
+ return nil
+ }
+
+ lat2, lon2 := msg2.GetLatLon()
+
+ if lat2 == 0 && lon2 == 0 {
+ return nil
+ }
+
+ dist, _ := DistBea(lat1, lon1, lat2, lon2)
+
+ if dist > 25 {
+ return &Pos{
+ time: cot.TimeFromMillis(msg2.TakMessage.GetCotEvent().GetSendTime()),
+ lat: lat2,
+ lon: lon2,
+ speed: msg2.TakMessage.GetCotEvent().GetDetail().GetTrack().GetSpeed(),
+ }
+ }
+
+ return nil
+}
diff --git a/staticfiles/static/js/main.js b/staticfiles/static/js/main.js
index 9f07f76..02217d8 100644
--- a/staticfiles/static/js/main.js
+++ b/staticfiles/static/js/main.js
@@ -46,6 +46,7 @@ let app = new Vue({
el: '#app',
data: {
units: new Map(),
+ connections: new Map(),
alert: null,
ts: 0,
},
@@ -55,11 +56,15 @@ let app = new Vue({
this.timer = setInterval(this.renew, 3000);
},
computed: {
+ all_conns: function () {
+ return this.ts && this.connections.values();
+ },
},
methods: {
renew: function () {
let vm = this;
let units = vm.units;
+ let conns = vm.connections;
fetch('/units')
.then(function (response) {
@@ -72,6 +77,17 @@ let app = new Vue({
});
vm.ts += 1;
});
+ fetch('/connections')
+ .then(function (response) {
+ return response.json()
+ })
+ .then(function (data) {
+ conns.clear();
+ data.forEach(function (i) {
+ conns.set(i.uid, i);
+ });
+ vm.ts += 1;
+ });
},
byCategory: function (s) {
let arr = Array.from(this.units.values()).filter(function (u) {