updating tools

This commit is contained in:
Kyle Isom 2024-01-27 13:53:15 -08:00
parent 14f29f88d3
commit ec18e60869
8 changed files with 425 additions and 89 deletions

45
cmd/ft8grid/main.go Normal file
View File

@ -0,0 +1,45 @@
package main
import (
"flag"
"fmt"
"git.wntrmute.dev/kyle/goutils/die"
"git.wntrmute.dev/kyle/gridsq/ft8"
"path/filepath"
)
func main() {
defaultDir := ft8.GetDefaultDirectory()
defaultLog := filepath.Join(defaultDir, "ALL.TXT")
var logFile string
flag.StringVar(&logFile, "f", defaultLog, "path to logfile")
flag.Parse()
if flag.NArg() == 0 {
die.With("No callsign specified!")
}
records, err := ft8.ProcessFile(logFile)
die.If(err)
grids := map[string]bool{}
who := flag.Arg(0)
fmt.Println("find grid for ", who)
for _, rec := range records {
if !rec.HasGrid() {
continue
}
if rec.De != who {
continue
}
if grids[rec.Grid] {
continue
}
grids[rec.Grid] = true
fmt.Printf("%s: %s\n", rec.De, rec.Grid)
}
}

186
cmd/gridsq/main.go Normal file
View File

@ -0,0 +1,186 @@
package main
import (
"errors"
"flag"
"fmt"
"regexp"
"strconv"
"strings"
"git.wntrmute.dev/kyle/goutils/die"
"github.com/klaus-tockloth/coco"
"github.com/logocomune/maidenhead"
geo "gopkg.in/billups/golang-geo.v2"
)
var qths = map[int]string{
4: "CM97",
6: "CM97cq",
8: "CM97cq53",
10: "CM97cq53CI",
}
var mgrsAccuracyToMaidenheadDigits = map[int]int{
1: 10,
10: 10,
100: 10,
1000: 8,
10000: 6,
}
var kmToMile = 0.621371
func cToP(c maidenhead.Coordinate) *geo.Point {
return geo.NewPoint(c.Lat, c.Lng)
}
func distBearing(qth, qsl string) (err error) {
var cqth, cqsl maidenhead.Coordinate
cqth.Lat, cqth.Lng, err = maidenhead.GridCenter(qth)
if err != nil {
return err
}
cqsl.Lat, cqsl.Lng, err = maidenhead.GridCenter(qsl)
if err != nil {
return err
}
pqth := cToP(cqth)
pqsl := cToP(cqsl)
dist := pqth.GreatCircleDistance(pqsl)
bearing := pqth.BearingTo(pqsl)
fmt.Printf("%s => %s: distance %0.2f miles, bearing %0.1f°\n",
qth, qsl, dist*kmToMile, bearing)
return nil
}
var utmZoneRegexp = regexp.MustCompile(`^(\d+)(\w+)$`)
var utmNorthingRegexp = regexp.MustCompile(`^(\d+)\w*[nsNS]$`)
var utmEastingRegexp = regexp.MustCompile(`^(\d+)\w+[ewEW]$`)
var errInvalidUTM = errors.New("invalid UTM coordinate")
func stringToUTM(s string) coco.UTM {
utm := coco.UTM{
ZoneNumber: 0,
ZoneLetter: 0,
Easting: 0,
Northing: 0,
}
fields := strings.Fields(s)
if len(fields) != 3 {
die.If(errInvalidUTM)
}
zone := utmZoneRegexp.FindStringSubmatch(fields[0])
if len(zone) != 3 {
fmt.Println(zone)
die.If(errInvalidUTM)
}
var err error
utm.ZoneNumber, err = strconv.Atoi(zone[1])
if err != nil {
die.If(errors.Join(err, errInvalidUTM))
}
utm.ZoneLetter = zone[2][0]
easting := utmEastingRegexp.FindStringSubmatch(fields[1])
if len(easting) == 0 {
die.If(errInvalidUTM)
}
utm.Easting, err = strconv.ParseFloat(easting[1], 64)
if err != nil {
die.If(errors.Join(err, errInvalidUTM))
}
northing := utmNorthingRegexp.FindStringSubmatch(fields[2])
if len(northing) == 0 {
die.If(errInvalidUTM)
}
utm.Northing, err = strconv.ParseFloat(northing[1], 64)
if err != nil {
die.If(errors.Join(err, errInvalidUTM))
}
return utm
}
func main() {
var lat, lon float64
var precision int64
flag.Float64Var(&lat, "lat", 0.0, "latitude of grid")
flag.Float64Var(&lon, "lon", 0.0, "longitude of grid")
flag.Int64Var(&precision, "d", 6, "number of digits")
flag.Parse()
if flag.NArg() == 0 {
fmt.Println("nothing")
return
}
cmd := flag.Arg(0)
switch cmd {
case "geo":
grid, err := maidenhead.Locator(lat, lon, int(precision))
die.If(err)
fmt.Printf("%0.6f %0.6f: ", lat, lon)
fmt.Println(grid)
case "qth":
grid, ok := qths[int(precision)]
if !ok {
fmt.Println(qths[6])
} else {
fmt.Println(grid)
}
case "db":
var qth, qsl string
if flag.NArg() == 2 {
qsl = flag.Arg(1)
qth = qths[len(qsl)]
} else {
qsl = flag.Arg(2)
qth = flag.Arg(1)
}
err := distBearing(qth, qsl)
die.If(err)
case "mgrs":
if flag.NArg() == 0 {
return
}
utm := coco.MGRS(strings.Replace(flag.Arg(1), " ", "", -1))
ll, accuracy, err := utm.ToLL()
fmt.Printf("%#v\n", ll)
die.If(err)
precision, ok := mgrsAccuracyToMaidenheadDigits[accuracy]
if !ok {
precision = 4
}
grid, err := maidenhead.Locator(ll.Lat, ll.Lon, precision)
die.If(err)
fmt.Println(grid)
case "utm":
if flag.NArg() == 0 {
return
}
utm := stringToUTM(flag.Arg(1))
ll, err := utm.ToLL()
fmt.Printf("%#v\n", ll)
die.If(err)
grid, err := maidenhead.Locator(ll.Lat, ll.Lon, 8)
die.If(err)
fmt.Println(grid)
}
}

12
cmd/kepfetch/main.go Normal file
View File

@ -0,0 +1,12 @@
package main
type Kep struct {
Filename string
URL string
}
type User struct {
Name string
Directory string
Prefix string
}

161
ft8/ft8.go Normal file
View File

@ -0,0 +1,161 @@
package ft8
import (
"bufio"
"git.wntrmute.dev/kyle/goutils/die"
"os"
"os/user"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"time"
)
func getHomeDirectory() string {
u, err := user.Current()
if err != nil {
return ""
}
return u.HomeDir
}
func GetDefaultDirectory() string {
switch runtime.GOOS {
case "linux":
return filepath.Join(
getHomeDirectory(),
".local",
"share",
"WSJT-X",
)
case "windows":
return filepath.Join(
getHomeDirectory(),
"AppData",
"Local",
"WSJT-X",
)
default:
die.With("Operating system '%s' isn't supported.", runtime.GOOS)
}
return ""
}
const (
RecordTypeCQ = iota + 1
RecordTypeCQReply
RecordTypeSignal
RecordTypeSignoff
)
// call ([\w\d/]+)
// grid (\w{2}\d{2})
var (
RegexpCQ = regexp.MustCompile(`^CQ ([\w/]+) (\w{2}\d{2})$`)
RegexpCQReply = regexp.MustCompile(`^([\w/]+) ([\w/]+) (\w{2}\d{2})$`)
RegexpRR73 = regexp.MustCompile(`^([\w\d/]+) ([\w\d/]+) RR73$`)
)
type Record struct {
Type uint8
Time time.Time
Freq float64
Tone int
Mode string
IsTX bool
De string
To string
Received int
Grid string
}
func (rec *Record) HasGrid() bool {
return rec.Grid != ""
}
func ParseRecordHeader(line string) (rec *Record, err error) {
// 0 1 2 3 4 5 6 7
// 231215_021330 14.074 Rx FT8 -6 0.1 1713 CQ N6ACA CM97
fields := strings.Fields(line)
rec = &Record{}
rec.Time, err = time.Parse("060102_150405", fields[0])
if err != nil {
return
}
rec.Freq, err = strconv.ParseFloat(fields[1], 64)
if err != nil {
return
}
if fields[2] == "Tx" {
rec.IsTX = true
}
rec.Mode = fields[3]
rec.Received, err = strconv.Atoi(fields[4])
if err != nil {
return
}
rec.Tone, err = strconv.Atoi(fields[6])
if err != nil {
return
}
return
}
func ParseRecord(line string) (*Record, error) {
rec, err := ParseRecordHeader(line)
if err != nil {
return nil, err
}
fields := strings.Fields(line)
line = strings.Join(fields[7:len(fields)], " ")
switch {
case RegexpRR73.MatchString(line):
rec.Type = RecordTypeSignoff
match := RegexpRR73.FindStringSubmatch(line)
rec.De = match[2]
rec.To = match[1]
case RegexpCQ.MatchString(line):
rec.Type = RecordTypeCQ
match := RegexpCQ.FindStringSubmatch(line)
rec.De = match[1]
rec.Grid = match[2]
case RegexpCQReply.MatchString(line):
rec.Type = RecordTypeCQReply
match := RegexpCQReply.FindStringSubmatch(line)
rec.To = match[1]
rec.De = match[2]
rec.Grid = match[3]
}
return rec, err
}
func ProcessFile(path string) ([]*Record, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var records []*Record
scanner := bufio.NewScanner(file)
for scanner.Scan() {
rec, err := ParseRecord(scanner.Text())
if err != nil {
return nil, err
}
records = append(records, rec)
}
return records, nil
}

18
ft8/ft8_test.go Normal file
View File

@ -0,0 +1,18 @@
package ft8
import "testing"
var (
testCQLine = `231215_021330 14.074 Rx FT8 -6 0.1 1713 CQ N6ACA CM97`
)
func TestMatchCQ(t *testing.T) {
rec, err := ParseRecord(testCQLine)
if err != nil {
t.Fatal(err)
}
if rec.Type != RecordTypeCQ {
t.Fatalf("invalid record: expected %d, have %d", RecordTypeCQ, rec.Type)
}
}

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.21.3
require (
git.wntrmute.dev/kyle/goutils v1.7.4 // indirect
github.com/klaus-tockloth/coco v0.2.0 // indirect
github.com/logocomune/maidenhead v1.0.1 // indirect
gopkg.in/billups/golang-geo.v2 v2.0.0-20170124000346-c2931833be19 // indirect
)

2
go.sum
View File

@ -1,5 +1,7 @@
git.wntrmute.dev/kyle/goutils v1.7.4 h1:kbvUoxRwAEemz4jL52AUKaOipuCX8F8PGTQHS5V3lRY=
git.wntrmute.dev/kyle/goutils v1.7.4/go.mod h1:1PGn83Ac98KWyI6yfpCVyP1Ji61PX6lFpROxY+IoTJg=
github.com/klaus-tockloth/coco v0.2.0 h1:k2GWaR+dbWi/d9EIgNhSqmEshpTJSoWLGoCmPd559/Y=
github.com/klaus-tockloth/coco v0.2.0/go.mod h1:ZKByCKTYUR9wceWX7RUQmL1bDc0Q2SkbVfgrNs2UyIw=
github.com/logocomune/maidenhead v1.0.1 h1:WnBpC/LIhc81QnwDdxEPKHwSAspMCWwsYMuM9vQMFgA=
github.com/logocomune/maidenhead v1.0.1/go.mod h1:FLkUKuGyo4uKESNlXG8jgQgaTSApyR2ZZfq7m2Btmjs=
gopkg.in/billups/golang-geo.v2 v2.0.0-20170124000346-c2931833be19 h1:vu0Y9rNRRTFG70fnQ/W/CG4N629vz9uaqaY7R7fXnVo=

89
main.go
View File

@ -1,89 +0,0 @@
package main
import (
"flag"
"fmt"
"git.wntrmute.dev/kyle/goutils/die"
"github.com/logocomune/maidenhead"
geo "gopkg.in/billups/golang-geo.v2"
)
var qths = map[int]string{
4: "CM97",
6: "CM97cq",
8: "CM97cq53",
10: "CM97cq53CI",
}
var kmToMile = 0.621371
func cToP(c maidenhead.Coordinate) *geo.Point {
return geo.NewPoint(c.Lat, c.Lng)
}
func distBearing(qth, qsl string) (err error) {
var cqth, cqsl maidenhead.Coordinate
cqth.Lat, cqth.Lng, err = maidenhead.GridCenter(qth)
if err != nil {
return err
}
cqsl.Lat, cqsl.Lng, err = maidenhead.GridCenter(qsl)
if err != nil {
return err
}
pqth := cToP(cqth)
pqsl := cToP(cqsl)
dist := pqth.GreatCircleDistance(pqsl)
bearing := pqth.BearingTo(pqsl)
fmt.Printf("%s => %s: distance %0.2f miles, bearing %0.1f°\n",
qth, qsl, dist*kmToMile, bearing)
return nil
}
func main() {
var lat, lon float64
var precision int64
flag.Float64Var(&lat, "lat", 0.0, "latitude of grid")
flag.Float64Var(&lon, "lon", 0.0, "longitude of grid")
flag.Int64Var(&precision, "d", 6, "number of digits")
flag.Parse()
if flag.NArg() == 0 {
fmt.Println("nothing")
return
}
cmd := flag.Arg(0)
switch cmd {
case "geo":
grid, err := maidenhead.Locator(lat, lon, int(precision))
die.If(err)
fmt.Printf("%0.6f %0.6f: ", lat, lon)
fmt.Println(grid)
case "qth":
grid, ok := qths[int(precision)]
if !ok {
fmt.Println(qths[6])
} else {
fmt.Println(grid)
}
case "db":
var qth, qsl string
if flag.NArg() == 2 {
qsl = flag.Arg(1)
qth = qths[len(qsl)]
} else {
qsl = flag.Arg(2)
qth = flag.Arg(1)
}
err := distBearing(qth, qsl)
die.If(err)
}
}