Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ The `-o` or `--overwrite` flag indicates that, if the output file already exists

The `-i` or `--interpolate` flag will interpolate the missing non-entered times based on the first and the last entered time.

ADIF exports declare ADIF 3.1.7. Decimal-degree `mylat` and `mylon`
values are converted to the ADIF `MY_LAT` and `MY_LON` location format.
POTA and WWFF exports include both the generic `SIG` fields used by existing
consumers and the dedicated ADIF `POTA_REF` or `WWFF_REF` fields.

### Example: generate an ADIF file for WWFF upload

To generate a WWFF-ready ADIF file:
Expand Down
43 changes: 40 additions & 3 deletions fleprocess/adif_write.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ limitations under the License.

import (
"fmt"
"math"
"strconv"
"strings"
"time"
)
Expand All @@ -37,7 +39,7 @@ func buildAdif(fullLog []LogLine, adifParams AdifParams) (adifList []string) {
//Print the fixed header
adifList = append(adifList, "ADIF Export for Fast Log Entry CLI by ON4KJM")
adifList = append(adifList, "<PROGRAMID:6>FLEcli")
adifList = append(adifList, "<ADIF_VER:5>3.1.0")
adifList = append(adifList, "<ADIF_VER:5>3.1.7")
adifList = append(adifList, "<EOH>")

for _, logLine := range fullLog {
Expand Down Expand Up @@ -68,17 +70,21 @@ func buildAdif(fullLog []LogLine, adifParams AdifParams) (adifList []string) {
if adifParams.IsWWFF {
adifLine.WriteString(adifElement("MY_SIG", "WWFF"))
adifLine.WriteString(adifElement("MY_SIG_INFO", logLine.MyWWFF))
adifLine.WriteString(adifElement("MY_WWFF_REF", logLine.MyWWFF))
if logLine.WWFF != "" {
adifLine.WriteString(adifElement("SIG", "WWFF"))
adifLine.WriteString(adifElement("SIG_INFO", logLine.WWFF))
adifLine.WriteString(adifElement("WWFF_REF", logLine.WWFF))
}
}
if adifParams.IsPOTA {
adifLine.WriteString(adifElement("MY_SIG", "POTA"))
adifLine.WriteString(adifElement("MY_SIG_INFO", logLine.MyPOTA))
adifLine.WriteString(adifElement("MY_POTA_REF", logLine.MyPOTA))
if logLine.POTA != "" {
adifLine.WriteString(adifElement("SIG", "POTA"))
adifLine.WriteString(adifElement("SIG_INFO", logLine.POTA))
adifLine.WriteString(adifElement("POTA_REF", logLine.POTA))
}
}
if adifParams.IsSOTA {
Expand All @@ -95,10 +101,10 @@ func buildAdif(fullLog []LogLine, adifParams AdifParams) (adifList []string) {
}

if logLine.MyLat != "" {
adifLine.WriteString(adifElement("MY_LAT", logLine.MyLat))
adifLine.WriteString(adifElement("MY_LAT", adifLocation(logLine.MyLat, true)))
}
if logLine.MyLon != "" {
adifLine.WriteString(adifElement("MY_LON", logLine.MyLon))
adifLine.WriteString(adifElement("MY_LON", adifLocation(logLine.MyLon, false)))
}
if logLine.MyCounty != "" {
adifLine.WriteString(adifElement("MY_CNTY", logLine.MyCounty))
Expand All @@ -115,6 +121,37 @@ func buildAdif(fullLog []LogLine, adifParams AdifParams) (adifList []string) {
return adifList
}

// adifLocation converts decimal degrees to the ADIF Location data type:
// XDDD MM.MMM, where X is N/S for latitude or E/W for longitude.
func adifLocation(decimalDegrees string, isLatitude bool) string {
value, err := strconv.ParseFloat(decimalDegrees, 64)
if err != nil {
return decimalDegrees
}

direction := "E"
if isLatitude {
direction = "N"
}
if math.Signbit(value) {
if isLatitude {
direction = "S"
} else {
direction = "W"
}
}

absoluteValue := math.Abs(value)
degrees := int(absoluteValue)
minutes := math.Round((absoluteValue-float64(degrees))*60*1000) / 1000
if minutes >= 60 {
degrees++
minutes = 0
}

return fmt.Sprintf("%s%03d %06.3f", direction, degrees, minutes)
}

// adifElement generated the ADIF sub-element
func adifElement(elementName, elementValue string) (element string) {
return fmt.Sprintf("<%s:%d>%s ", strings.ToUpper(elementName), len(elementValue), elementValue)
Expand Down
48 changes: 36 additions & 12 deletions fleprocess/adif_write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,46 @@ func Test_adifElement(t *testing.T) {
}
}

func TestAdifLocation(t *testing.T) {
tests := []struct {
name string
decimalDegrees string
isLatitude bool
want string
}{
{name: "north latitude", decimalDegrees: "42.4891", isLatitude: true, want: "N042 29.346"},
{name: "south latitude", decimalDegrees: "-42.4891", isLatitude: true, want: "S042 29.346"},
{name: "east longitude", decimalDegrees: "71.8865", isLatitude: false, want: "E071 53.190"},
{name: "west longitude", decimalDegrees: "-71.8865", isLatitude: false, want: "W071 53.190"},
{name: "rounding carries into degrees", decimalDegrees: "89.999999", isLatitude: true, want: "N090 00.000"},
{name: "invalid input is preserved", decimalDegrees: "invalid", isLatitude: true, want: "invalid"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := adifLocation(tt.decimalDegrees, tt.isLatitude); got != tt.want {
t.Fatalf("adifLocation() = %q, want %q", got, tt.want)
}
})
}
}

func Test_buildAdif(t *testing.T) {
sampleFilledLog1 := []LogLine{
{MyCall: "ON4KJM/P", Call: "S57LC", Date: "2020-05-24", Time: "1310", Band: "20m", Frequency: "14.045", Mode: "CW", RSTsent: "599", RSTrcvd: "599", MyWWFF: "ONFF-0259", Operator: "ON4KJM", Nickname: "ONFF-0259-1"},
{MyCall: "ON4KJM/P", Call: "ON4LY", Date: "2020-05-24", Time: "1312", Band: "20m", Mode: "CW", RSTsent: "559", RSTrcvd: "599", MyWWFF: "ONFF-0259", Operator: "ON4KJM"},
}
expectedADIFtitle := "ADIF Export for Fast Log Entry CLI by ON4KJM"
expectedADIFprogramId := "<PROGRAMID:6>FLEcli"
expectedADIFversion := "<ADIF_VER:5>3.1.0"
expectedADIFversion := "<ADIF_VER:5>3.1.7"

expectedOutput1 := []string{
expectedADIFtitle,
expectedADIFprogramId,
expectedADIFversion,
"<EOH>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <OPERATOR:6>ON4KJM <APP_EQSL_QTH_NICKNAME:11>ONFF-0259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <OPERATOR:6>ON4KJM <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <MY_WWFF_REF:9>ONFF-0259 <OPERATOR:6>ON4KJM <APP_EQSL_QTH_NICKNAME:11>ONFF-0259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <MY_WWFF_REF:9>ONFF-0259 <OPERATOR:6>ON4KJM <EOR>",
}

sampleFilledLog2 := []LogLine{
Expand All @@ -79,8 +103,8 @@ func Test_buildAdif(t *testing.T) {
expectedADIFprogramId,
expectedADIFversion,
"<EOH>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <GRIDSQUARE:4>JO50 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <APP_EQSL_QTH_NICKNAME:11>ONFF-0259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <GRIDSQUARE:4>JO50 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <MY_WWFF_REF:9>ONFF-0259 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <APP_EQSL_QTH_NICKNAME:11>ONFF-0259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <MY_WWFF_REF:9>ONFF-0259 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <EOR>",
}

sampleFilledLog3 := []LogLine{
Expand All @@ -93,8 +117,8 @@ func Test_buildAdif(t *testing.T) {
expectedADIFprogramId,
expectedADIFversion,
"<EOH>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <GRIDSQUARE:4>JO50 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <APP_EQSL_QTH_NICKNAME:11>ONFF-0259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <SIG:4>WWFF <SIG_INFO:9>DLFF-0001 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <GRIDSQUARE:4>JO50 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <MY_WWFF_REF:9>ONFF-0259 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <APP_EQSL_QTH_NICKNAME:11>ONFF-0259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <MY_WWFF_REF:9>ONFF-0259 <SIG:4>WWFF <SIG_INFO:9>DLFF-0001 <WWFF_REF:9>DLFF-0001 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <EOR>",
}

sampleFilledLogPOTA := []LogLine{
Expand All @@ -107,8 +131,8 @@ func Test_buildAdif(t *testing.T) {
expectedADIFprogramId,
expectedADIFversion,
"<EOH>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <OPERATOR:6>ON4KJM <APP_EQSL_QTH_NICKNAME:10>ON-00259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <OPERATOR:6>ON4KJM <MY_CNTY:10>Ham County <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <MY_POTA_REF:8>ON-00259 <OPERATOR:6>ON4KJM <APP_EQSL_QTH_NICKNAME:10>ON-00259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <MY_POTA_REF:8>ON-00259 <OPERATOR:6>ON4KJM <MY_CNTY:10>Ham County <EOR>",
}

sampleFilledLogPOTA2 := []LogLine{
Expand All @@ -122,9 +146,9 @@ func Test_buildAdif(t *testing.T) {
expectedADIFprogramId,
expectedADIFversion,
"<EOH>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <GRIDSQUARE:4>JO50 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <APP_EQSL_QTH_NICKNAME:10>ON-00259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <SIG:4>POTA <SIG_INFO:8>DL-00001 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <SIG:4>POTA <SIG_INFO:8>DL-00001 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <MY_LAT:10>15.1234567 <MY_LON:12>-123.1234567 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <GRIDSQUARE:4>JO50 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <MY_POTA_REF:8>ON-00259 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <APP_EQSL_QTH_NICKNAME:10>ON-00259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <MY_POTA_REF:8>ON-00259 <SIG:4>POTA <SIG_INFO:8>DL-00001 <POTA_REF:8>DL-00001 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <MY_POTA_REF:8>ON-00259 <SIG:4>POTA <SIG_INFO:8>DL-00001 <POTA_REF:8>DL-00001 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <MY_LAT:11>N015 07.407 <MY_LON:11>W123 07.407 <EOR>",
}

type args struct {
Expand Down