Skip to content

Commit

Permalink
add quality tags according to CT1BOH's algorithm to the bandmap entries
Browse files Browse the repository at this point in the history
  • Loading branch information
ftl committed May 18, 2024
1 parent 7f29a5f commit 81cecd3
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 18 deletions.
60 changes: 47 additions & 13 deletions core/bandmap/entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import (

const (
// spots within this distance to an entry's frequency will be added to the entry
spotFrequencyDeltaThreshold float64 = 500
spotFrequencyDeltaThreshold float64 = 300
// the frequency of an entry is aligend to this is grid of accuracy
spotFrequencyStep float64 = 10
// at least this number of spots of the same callsign on the same frequency are required for a valid spot
spotValidThreshold = 3
)

type Entry struct {
Expand Down Expand Up @@ -43,24 +45,44 @@ func (e *Entry) Len() int {
return len(e.spots)
}

func (e *Entry) Matches(spot core.Spot) bool {
if spot.Call != e.Call {
return false
}
func (e *Entry) Matches(spot core.Spot) (core.SpotQuality, bool) {
if spot.Band != e.Band {
return false
return core.UnknownSpotQuality, false
}
if spot.Mode != core.NoMode && e.Mode != core.NoMode && spot.Mode != e.Mode {
return false
return core.UnknownSpotQuality, false
}

var callsignDistance int
if spot.Call == e.Call {
callsignDistance = 0
} else {
callsignDistance = calculateCallsignDistance(spot.Call, e.Call)
}

frequencyDelta := math.Abs(float64(e.Frequency - spot.Frequency))
return frequencyDelta <= spotFrequencyDeltaThreshold
onFrequency := frequencyDelta <= spotFrequencyDeltaThreshold

quality := core.UnknownSpotQuality
if len(e.spots)+1 >= spotValidThreshold {
quality = core.ValidSpotQuality
}

if callsignDistance == 0 && onFrequency {
return quality, true
} else if e.Quality == core.ValidSpotQuality && callsignDistance == 0 && !onFrequency {
return core.QSYSpotQuality, false
} else if e.Quality == core.ValidSpotQuality && callsignDistance <= similarCallsignThreshold && onFrequency {
return core.BustedSpotQuality, false
} else {
return core.UnknownSpotQuality, false
}
}

func (e *Entry) Add(spot core.Spot) bool {
if !e.Matches(spot) {
return false
func (e *Entry) Add(spot core.Spot) (core.SpotQuality, bool) {
quality, match := e.Matches(spot)
if !match {
return quality, false
}

e.spots = append(e.spots, spot)
Expand All @@ -71,8 +93,11 @@ func (e *Entry) Add(spot core.Spot) bool {
if e.Source.Priority() > spot.Source.Priority() {
e.Source = spot.Source
}
if quality == core.ValidSpotQuality {
e.Quality = quality
}

return true
return quality, true
}

func (e *Entry) RemoveSpotsBefore(timestamp time.Time) bool {
Expand Down Expand Up @@ -106,6 +131,9 @@ func (e *Entry) update() {
e.LastHeard = lastHeard
e.Source = source
e.SpotCount = len(e.spots)
if e.SpotCount < spotValidThreshold && e.Quality == core.ValidSpotQuality {
e.Quality = core.UnknownSpotQuality
}
}

func (e *Entry) updateFrequency() bool {
Expand Down Expand Up @@ -222,16 +250,22 @@ func (l *Entries) Len() int {
}

func (l *Entries) Add(spot core.Spot, now time.Time, weights core.BandmapWeights) {
entryQuality := core.UnknownSpotQuality
for _, e := range l.entries {
if e.Add(spot) {
quality, added := e.Add(spot)
if added {
e.Info = l.callinfo.GetInfo(spot.Call, spot.Band, spot.Mode, []string{})
e.Info.WeightedValue = l.calculateWeightedValue(e, now, weights)
l.emitEntryUpdated(*e)
return
}
if entryQuality < quality {
entryQuality = quality
}
}

newEntry := NewEntry(spot)
newEntry.Quality = entryQuality
if newEntry.Call.String() != "" {
newEntry.Info = l.callinfo.GetInfo(newEntry.Call, newEntry.Band, newEntry.Mode, []string{})
newEntry.Info.WeightedValue = l.calculateWeightedValue(&newEntry, now, weights)
Expand Down
53 changes: 52 additions & 1 deletion core/bandmap/entries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestEntry_Add_OnlySameCallAndSimilarFrequency(t *testing.T) {
},
}

added := entry.Add(core.Spot{Call: callsign.MustParse(tc.call), Frequency: tc.frequency})
_, added := entry.Add(core.Spot{Call: callsign.MustParse(tc.call), Frequency: tc.frequency})
assert.Equal(t, tc.valid, added)
})
}
Expand Down Expand Up @@ -382,6 +382,57 @@ func TestEntries_Notify(t *testing.T) {
assert.Equal(t, "DL1ABC", listener.removed[0].Call.String())
}

func TestEntry_Matches(t *testing.T) {
now := time.Now()
spot := core.Spot{Call: callsign.MustParse("dl1abc"), Frequency: 3535000, Time: now.Add(-5 * time.Minute)}
entry := NewEntry(spot)
assert.Equal(t, core.UnknownSpotQuality, entry.Quality)

similarSpot := core.Spot{Call: callsign.MustParse("dl2abc"), Frequency: 3535000, Time: now.Add(-2 * time.Minute)}
quality, match := entry.Matches(similarSpot)
assert.False(t, match)
assert.Equal(t, core.UnknownSpotQuality, quality)
_, added := entry.Add(similarSpot)
assert.False(t, added)

quality, match = entry.Matches(spot)
assert.True(t, match)
assert.Equal(t, core.UnknownSpotQuality, quality)

_, added = entry.Add(spot)
assert.True(t, added)
assert.Equal(t, core.UnknownSpotQuality, entry.Quality)

quality, match = entry.Matches(spot)
assert.True(t, match)
assert.Equal(t, core.ValidSpotQuality, quality)

_, added = entry.Add(spot)
assert.True(t, added)
assert.Equal(t, core.ValidSpotQuality, entry.Quality)

qsySpot := core.Spot{Call: callsign.MustParse("dl1abc"), Frequency: 3545000, Time: now.Add(-2 * time.Minute)}
quality, match = entry.Matches(qsySpot)
assert.False(t, match)
assert.Equal(t, core.QSYSpotQuality, quality)
_, added = entry.Add(qsySpot)
assert.False(t, added)

bustedSpot := core.Spot{Call: callsign.MustParse("dl2abc"), Frequency: 3535000, Time: now.Add(-2 * time.Minute)}
quality, match = entry.Matches(bustedSpot)
assert.False(t, match)
assert.Equal(t, core.BustedSpotQuality, quality)
_, added = entry.Add(bustedSpot)
assert.False(t, added)

completeDifferentSpot := core.Spot{Call: callsign.MustParse("dl3xyz"), Frequency: 3535000, Time: now.Add(-2 * time.Minute)}
quality, match = entry.Matches(completeDifferentSpot)
assert.False(t, match)
assert.Equal(t, core.UnknownSpotQuality, quality)
_, added = entry.Add(completeDifferentSpot)
assert.False(t, added)
}

func TestFilterSlice(t *testing.T) {
input := []int{1, 10, 5, 2, 9, 7, 6, 3, 4}

Expand Down
10 changes: 6 additions & 4 deletions core/bandmap/false_entry_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,17 @@ func CheckFalseEntry(entry1, entry2 core.BandmapEntry) FalseEntryCheckResult {
}
}

func calculateCallsignDistance(call1, call2 callsign.Callsign) int {
options := levenshtein.DefaultOptions
return levenshtein.DistanceForStrings([]rune(call1.String()), []rune(call2.String()), options)
}

func checkLocallyVerified(entry core.BandmapEntry) bool {
return entry.Source == core.WorkedSpot || entry.Source == core.ManualSpot
}

func checkCallsignSimilar(call1, call2 callsign.Callsign) bool {
options := levenshtein.DefaultOptions
distance := levenshtein.DistanceForStrings([]rune(call1.String()), []rune(call2.String()), options)

return distance <= similarCallsignThreshold
return calculateCallsignDistance(call1, call2) <= similarCallsignThreshold
}

func checkFirstSpotCountIsFalse(count1, count2 int) bool {
Expand Down
20 changes: 20 additions & 0 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,25 @@ func (t SpotType) Priority() int {
return priority
}

type SpotQuality int

const (
UnknownSpotQuality SpotQuality = iota
BustedSpotQuality
QSYSpotQuality
ValidSpotQuality
)

const SpotQualityTags = "?BQV"

func (q SpotQuality) Tag() string {
i := int(q)
if i > 0 && i < len(SpotQualityTags) {
return string(SpotQualityTags[q])
}
return string(SpotQualityTags[0])
}

type SpotFilter string

const (
Expand Down Expand Up @@ -853,6 +872,7 @@ type BandmapEntry struct {
LastHeard time.Time
Source SpotType
SpotCount int
Quality SpotQuality

Info Callinfo
}
Expand Down
6 changes: 6 additions & 0 deletions ui/spotsTableView.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
const (
spotColumnFrequency = iota
spotColumnCallsign
spotColumnQualityTag
spotColumnPredictedExchange
spotColumnPoints
spotColumnMultis
Expand All @@ -38,6 +39,7 @@ func setupSpotsTableView(v *spotsView, builder *gtk.Builder, controller SpotsCon

v.table.AppendColumn(createSpotMarkupColumn("Frequency", spotColumnFrequency))
v.table.AppendColumn(createSpotMarkupColumn("Callsign", spotColumnCallsign))
v.table.AppendColumn(createSpotTextColumn("T", spotColumnQualityTag))
v.table.AppendColumn(createSpotTextColumn("Exchange", spotColumnPredictedExchange))
v.table.AppendColumn(createSpotMarkupColumn("Pts", spotColumnPoints))
v.table.AppendColumn(createSpotMarkupColumn("Mult", spotColumnMultis))
Expand Down Expand Up @@ -135,6 +137,7 @@ func (v *spotsView) fillEntryToTableRow(row *gtk.TreeIter, entry core.BandmapEnt
[]int{
spotColumnFrequency,
spotColumnCallsign,
spotColumnQualityTag,
spotColumnPredictedExchange,
spotColumnPoints,
spotColumnMultis,
Expand All @@ -148,6 +151,7 @@ func (v *spotsView) fillEntryToTableRow(row *gtk.TreeIter, entry core.BandmapEnt
[]any{
formatSpotFrequency(entry.Frequency, entry.ProximityFactor(v.currentFrame.Frequency), entry.OnFrequency(v.currentFrame.Frequency)),
formatSpotCall(entry.Call, entry.ProximityFactor(v.currentFrame.Frequency), entry.OnFrequency(v.currentFrame.Frequency)),
entry.Quality.Tag(),
entry.Info.ExchangeText,
formatPoints(entry.Info.Points, entry.Info.Duplicate, 1),
formatPoints(entry.Info.Multis, entry.Info.Duplicate, 0),
Expand Down Expand Up @@ -229,12 +233,14 @@ func (v *spotsView) updateHighlightedColumns(entry core.BandmapEntry) error {
[]int{
spotColumnFrequency,
spotColumnCallsign,
spotColumnQualityTag,
spotColumnAge,
spotColumnWeightedValue,
},
[]any{
formatSpotFrequency(entry.Frequency, entry.ProximityFactor(v.currentFrame.Frequency), entry.OnFrequency(v.currentFrame.Frequency)),
formatSpotCall(entry.Call, entry.ProximityFactor(v.currentFrame.Frequency), entry.OnFrequency(v.currentFrame.Frequency)),
entry.Quality.Tag(),
formatSpotAge(entry.LastHeard),
fmt.Sprintf("%.1f", entry.Info.WeightedValue),
},
Expand Down

0 comments on commit 81cecd3

Please sign in to comment.