Restructure assets to top-level package and add Reset Map button

- Move assets from internal/assets to top-level assets/ package for clean embed directive
- Consolidate all static files in single location (assets/static/)
- Remove duplicate static file locations to maintain single source of truth
- Add Reset Map button to map controls with full functionality
- Implement resetMap() method to return map to calculated origin position
- Store origin in this.mapOrigin for reset functionality
- Fix go:embed pattern to work without parent directory references

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2025-08-24 00:57:49 +02:00
commit 1425f0a018
20 changed files with 263 additions and 2139 deletions

View file

@ -90,7 +90,7 @@ func (d *Decoder) Decode(data []byte) (*Aircraft, error) {
df := (data[0] >> 3) & 0x1F
icao := d.extractICAO(data, df)
aircraft := &Aircraft{
ICAO24: icao,
}
@ -154,49 +154,49 @@ func (d *Decoder) decodeExtendedSquitter(data []byte, aircraft *Aircraft) (*Airc
// decodeIdentification extracts callsign and category
func (d *Decoder) decodeIdentification(data []byte, aircraft *Aircraft) {
tc := (data[4] >> 3) & 0x1F
// Category
aircraft.Category = d.getAircraftCategory(tc, data[4]&0x07)
// Callsign - 8 characters encoded in 6 bits each
chars := "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"
callsign := ""
// Extract 48 bits starting from bit 40
for i := 0; i < 8; i++ {
bitOffset := 40 + i*6
byteOffset := bitOffset / 8
bitShift := bitOffset % 8
var charCode uint8
if bitShift <= 2 {
charCode = (data[byteOffset] >> (2 - bitShift)) & 0x3F
} else {
charCode = ((data[byteOffset] << (bitShift - 2)) & 0x3F) |
(data[byteOffset+1] >> (10 - bitShift))
charCode = ((data[byteOffset] << (bitShift - 2)) & 0x3F) |
(data[byteOffset+1] >> (10 - bitShift))
}
if charCode < 64 {
callsign += string(chars[charCode])
}
}
aircraft.Callsign = callsign
}
// decodeAirbornePosition extracts position from CPR encoded data
func (d *Decoder) decodeAirbornePosition(data []byte, aircraft *Aircraft) {
tc := (data[4] >> 3) & 0x1F
// Altitude
altBits := (uint16(data[5])<<4 | uint16(data[6])>>4) & 0x0FFF
aircraft.Altitude = d.decodeAltitudeBits(altBits, tc)
// CPR latitude/longitude
cprLat := uint32(data[6]&0x03)<<15 | uint32(data[7])<<7 | uint32(data[8])>>1
cprLon := uint32(data[8]&0x01)<<16 | uint32(data[9])<<8 | uint32(data[10])
oddFlag := (data[6] >> 2) & 0x01
// Store CPR values for later decoding
if oddFlag == 1 {
d.cprOddLat[aircraft.ICAO24] = float64(cprLat) / 131072.0
@ -205,7 +205,7 @@ func (d *Decoder) decodeAirbornePosition(data []byte, aircraft *Aircraft) {
d.cprEvenLat[aircraft.ICAO24] = float64(cprLat) / 131072.0
d.cprEvenLon[aircraft.ICAO24] = float64(cprLon) / 131072.0
}
// Try to decode position if we have both even and odd messages
d.decodeCPRPosition(aircraft)
}
@ -214,42 +214,42 @@ func (d *Decoder) decodeAirbornePosition(data []byte, aircraft *Aircraft) {
func (d *Decoder) decodeCPRPosition(aircraft *Aircraft) {
evenLat, evenExists := d.cprEvenLat[aircraft.ICAO24]
oddLat, oddExists := d.cprOddLat[aircraft.ICAO24]
if !evenExists || !oddExists {
return
}
evenLon := d.cprEvenLon[aircraft.ICAO24]
oddLon := d.cprOddLon[aircraft.ICAO24]
// CPR decoding algorithm
dLat := 360.0 / 60.0
j := math.Floor(evenLat*59 - oddLat*60 + 0.5)
latEven := dLat * (math.Mod(j, 60) + evenLat)
latOdd := dLat * (math.Mod(j, 59) + oddLat)
if latEven >= 270 {
latEven -= 360
}
if latOdd >= 270 {
latOdd -= 360
}
// Choose the most recent position
aircraft.Latitude = latOdd // Use odd for now, should check timestamps
// Longitude calculation
nl := d.nlFunction(aircraft.Latitude)
ni := math.Max(nl-1, 1)
dLon := 360.0 / ni
m := math.Floor(evenLon*(nl-1) - oddLon*nl + 0.5)
lon := dLon * (math.Mod(m, ni) + oddLon)
if lon >= 180 {
lon -= 360
}
aircraft.Longitude = lon
}
@ -258,41 +258,41 @@ func (d *Decoder) nlFunction(lat float64) float64 {
if math.Abs(lat) >= 87 {
return 2
}
nz := 15.0
a := 1 - math.Cos(math.Pi/(2*nz))
b := math.Pow(math.Cos(math.Pi/180.0*math.Abs(lat)), 2)
nl := 2 * math.Pi / math.Acos(1-a/b)
return math.Floor(nl)
}
// decodeVelocity extracts speed and heading
func (d *Decoder) decodeVelocity(data []byte, aircraft *Aircraft) {
subtype := (data[4]) & 0x07
if subtype == 1 || subtype == 2 {
// Ground speed
ewRaw := uint16(data[5]&0x03)<<8 | uint16(data[6])
nsRaw := uint16(data[7])<<3 | uint16(data[8])>>5
ewVel := float64(ewRaw - 1)
nsVel := float64(nsRaw - 1)
if data[5]&0x04 != 0 {
ewVel = -ewVel
}
if data[7]&0x80 != 0 {
nsVel = -nsVel
}
aircraft.GroundSpeed = math.Sqrt(ewVel*ewVel + nsVel*nsVel)
aircraft.Track = math.Atan2(ewVel, nsVel) * 180 / math.Pi
if aircraft.Track < 0 {
aircraft.Track += 360
}
}
// Vertical rate
vrSign := (data[8] >> 3) & 0x01
vrBits := uint16(data[8]&0x07)<<6 | uint16(data[9])>>2
@ -315,20 +315,20 @@ func (d *Decoder) decodeAltitudeBits(altCode uint16, tc uint8) int {
if altCode == 0 {
return 0
}
// Gray code to binary conversion
var n uint16
for i := uint(0); i < 12; i++ {
n ^= altCode >> i
}
alt := int(n)*25 - 1000
if tc >= 20 && tc <= 22 {
// GNSS altitude
return alt
}
return alt
}
@ -398,7 +398,7 @@ func (d *Decoder) getAircraftCategory(tc uint8, ca uint8) string {
// decodeStatus handles aircraft status messages
func (d *Decoder) decodeStatus(data []byte, aircraft *Aircraft) {
subtype := data[4] & 0x07
if subtype == 1 {
// Emergency/priority status
emergency := (data[5] >> 5) & 0x07
@ -428,7 +428,7 @@ func (d *Decoder) decodeTargetState(data []byte, aircraft *Aircraft) {
if altBits != 0 {
aircraft.SelectedAltitude = int(altBits)*32 - 32
}
// Barometric pressure setting
baroBits := uint16(data[7])<<1 | uint16(data[8])>>7
if baroBits != 0 {
@ -447,25 +447,25 @@ func (d *Decoder) decodeOperationalStatus(data []byte, aircraft *Aircraft) {
// decodeSurfacePosition handles surface position messages
func (d *Decoder) decodeSurfacePosition(data []byte, aircraft *Aircraft) {
aircraft.OnGround = true
// Movement
movement := uint8(data[4]&0x07)<<4 | uint8(data[5])>>4
if movement > 0 && movement < 125 {
aircraft.GroundSpeed = d.decodeGroundSpeed(movement)
}
// Track
trackValid := (data[5] >> 3) & 0x01
if trackValid != 0 {
trackBits := uint16(data[5]&0x07)<<4 | uint16(data[6])>>4
aircraft.Track = float64(trackBits) * 360.0 / 128.0
}
// CPR position (similar to airborne)
cprLat := uint32(data[6]&0x03)<<15 | uint32(data[7])<<7 | uint32(data[8])>>1
cprLon := uint32(data[8]&0x01)<<16 | uint32(data[9])<<8 | uint32(data[10])
oddFlag := (data[6] >> 2) & 0x01
if oddFlag == 1 {
d.cprOddLat[aircraft.ICAO24] = float64(cprLat) / 131072.0
d.cprOddLon[aircraft.ICAO24] = float64(cprLon) / 131072.0
@ -473,7 +473,7 @@ func (d *Decoder) decodeSurfacePosition(data []byte, aircraft *Aircraft) {
d.cprEvenLat[aircraft.ICAO24] = float64(cprLat) / 131072.0
d.cprEvenLon[aircraft.ICAO24] = float64(cprLon) / 131072.0
}
d.decodeCPRPosition(aircraft)
}
@ -497,4 +497,4 @@ func (d *Decoder) decodeGroundSpeed(movement uint8) float64 {
return 175.0
}
return 0
}
}