updating tools
This commit is contained in:
parent
14f29f88d3
commit
ec18e60869
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
type Kep struct {
|
||||
Filename string
|
||||
URL string
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Directory string
|
||||
Prefix string
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
1
go.mod
|
@ -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
2
go.sum
|
@ -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
89
main.go
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue