Stricter decoding
This commit is contained in:
887
src/frame.ts
887
src/frame.ts
File diff suppressed because it is too large
Load Diff
@@ -1,82 +1,83 @@
|
||||
import { PacketSegment, PacketStructure } from "./parser.types";
|
||||
|
||||
export interface IAddress {
|
||||
call: string;
|
||||
ssid: string;
|
||||
call: string;
|
||||
ssid: string;
|
||||
isRepeated: boolean;
|
||||
}
|
||||
|
||||
export interface IFrame {
|
||||
source: IAddress;
|
||||
source: IAddress;
|
||||
destination: IAddress;
|
||||
path: IAddress[];
|
||||
payload: string;
|
||||
path: IAddress[];
|
||||
payload: string;
|
||||
}
|
||||
|
||||
// APRS Data Type Identifiers (first character of payload)
|
||||
export const DataTypeIdentifier = {
|
||||
// Position Reports
|
||||
PositionNoTimestampNoMessaging: '!',
|
||||
PositionNoTimestampWithMessaging: '=',
|
||||
PositionWithTimestampNoMessaging: '/',
|
||||
PositionWithTimestampWithMessaging: '@',
|
||||
PositionNoTimestampNoMessaging: "!",
|
||||
PositionNoTimestampWithMessaging: "=",
|
||||
PositionWithTimestampNoMessaging: "/",
|
||||
PositionWithTimestampWithMessaging: "@",
|
||||
|
||||
// Mic-E
|
||||
MicECurrent: '`',
|
||||
MicECurrent: "`",
|
||||
MicEOld: "'",
|
||||
|
||||
// Messages and Bulletins
|
||||
Message: ':',
|
||||
Message: ":",
|
||||
|
||||
// Objects and Items
|
||||
Object: ';',
|
||||
Item: ')',
|
||||
Object: ";",
|
||||
Item: ")",
|
||||
|
||||
// Status
|
||||
Status: '>',
|
||||
Status: ">",
|
||||
|
||||
// Query
|
||||
Query: '?',
|
||||
Query: "?",
|
||||
|
||||
// Telemetry
|
||||
TelemetryData: 'T',
|
||||
TelemetryData: "T",
|
||||
|
||||
// Weather
|
||||
WeatherReportNoPosition: '_',
|
||||
WeatherReportNoPosition: "_",
|
||||
|
||||
// Raw GPS Data
|
||||
RawGPS: '$',
|
||||
RawGPS: "$",
|
||||
|
||||
// Station Capabilities
|
||||
StationCapabilities: '<',
|
||||
StationCapabilities: "<",
|
||||
|
||||
// User-Defined
|
||||
UserDefined: '{',
|
||||
UserDefined: "{",
|
||||
|
||||
// Third-Party Traffic
|
||||
ThirdParty: '}',
|
||||
ThirdParty: "}",
|
||||
|
||||
// Invalid/Test Data
|
||||
InvalidOrTest: ',',
|
||||
InvalidOrTest: ",",
|
||||
} as const;
|
||||
|
||||
export type DataTypeIdentifier = typeof DataTypeIdentifier[keyof typeof DataTypeIdentifier];
|
||||
export type DataTypeIdentifier =
|
||||
(typeof DataTypeIdentifier)[keyof typeof DataTypeIdentifier];
|
||||
|
||||
export interface ISymbol {
|
||||
table: string; // Symbol table identifier
|
||||
code: string; // Symbol code
|
||||
table: string; // Symbol table identifier
|
||||
code: string; // Symbol code
|
||||
|
||||
toString(): string; // Return combined symbol representation (e.g., "tablecode")
|
||||
}
|
||||
|
||||
// Position data common to multiple formats
|
||||
export interface IPosition {
|
||||
latitude: number; // Decimal degrees
|
||||
longitude: number; // Decimal degrees
|
||||
latitude: number; // Decimal degrees
|
||||
longitude: number; // Decimal degrees
|
||||
ambiguity?: number; // Position ambiguity (0-4)
|
||||
altitude?: number; // Meters
|
||||
speed?: number; // Speed in knots/kmh depending on source
|
||||
course?: number; // Course in degrees
|
||||
altitude?: number; // Meters
|
||||
speed?: number; // Speed in knots/kmh depending on source
|
||||
course?: number; // Course in degrees
|
||||
symbol?: ISymbol;
|
||||
comment?: string;
|
||||
|
||||
@@ -86,22 +87,22 @@ export interface IPosition {
|
||||
}
|
||||
|
||||
export interface ITimestamp {
|
||||
day?: number; // Day of month (DHM format)
|
||||
month?: number; // Month (MDHM format)
|
||||
day?: number; // Day of month (DHM format)
|
||||
month?: number; // Month (MDHM format)
|
||||
hours: number;
|
||||
minutes: number;
|
||||
seconds?: number;
|
||||
format: 'DHM' | 'HMS' | 'MDHM'; // Day-Hour-Minute, Hour-Minute-Second, Month-Day-Hour-Minute
|
||||
zulu?: boolean; // Is UTC/Zulu time
|
||||
toDate(): Date; // Convert to Date object respecting timezone
|
||||
format: "DHM" | "HMS" | "MDHM"; // Day-Hour-Minute, Hour-Minute-Second, Month-Day-Hour-Minute
|
||||
zulu?: boolean; // Is UTC/Zulu time
|
||||
toDate(): Date; // Convert to Date object respecting timezone
|
||||
}
|
||||
|
||||
// Position Report Payload
|
||||
export interface PositionPayload {
|
||||
type: 'position';
|
||||
type: "position";
|
||||
timestamp?: ITimestamp;
|
||||
position: IPosition;
|
||||
messaging: boolean; // Whether APRS messaging is enabled
|
||||
messaging: boolean; // Whether APRS messaging is enabled
|
||||
micE?: {
|
||||
messageType?: string;
|
||||
isStandard?: boolean;
|
||||
@@ -117,50 +118,50 @@ export interface CompressedPosition {
|
||||
table: string;
|
||||
code: string;
|
||||
};
|
||||
course?: number; // Degrees
|
||||
speed?: number; // Knots
|
||||
range?: number; // Miles
|
||||
altitude?: number; // Feet
|
||||
course?: number; // Degrees
|
||||
speed?: number; // Knots
|
||||
range?: number; // Miles
|
||||
altitude?: number; // Feet
|
||||
radioRange?: number; // Miles
|
||||
compression: 'old' | 'current';
|
||||
compression: "old" | "current";
|
||||
}
|
||||
|
||||
// Mic-E Payload (compressed in destination address)
|
||||
export interface MicEPayload {
|
||||
type: 'mic-e';
|
||||
type: "mic-e";
|
||||
position: IPosition;
|
||||
course?: number;
|
||||
speed?: number;
|
||||
altitude?: number;
|
||||
messageType?: string; // Standard Mic-E message
|
||||
telemetry?: number[]; // Optional telemetry channels
|
||||
messageType?: string; // Standard Mic-E message
|
||||
telemetry?: number[]; // Optional telemetry channels
|
||||
status?: string;
|
||||
}
|
||||
|
||||
// Message Payload
|
||||
export interface MessagePayload {
|
||||
type: 'message';
|
||||
addressee: string; // 9 character padded callsign
|
||||
text: string; // Message text
|
||||
messageNumber?: string; // Message ID for acknowledgment
|
||||
ack?: string; // Acknowledgment of message ID
|
||||
reject?: string; // Rejection of message ID
|
||||
type: "message";
|
||||
addressee: string; // 9 character padded callsign
|
||||
text: string; // Message text
|
||||
messageNumber?: string; // Message ID for acknowledgment
|
||||
ack?: string; // Acknowledgment of message ID
|
||||
reject?: string; // Rejection of message ID
|
||||
}
|
||||
|
||||
// Bulletin/Announcement (variant of message)
|
||||
export interface BulletinPayload {
|
||||
type: 'bulletin';
|
||||
bulletinId: string; // Bulletin identifier (BLN#)
|
||||
type: "bulletin";
|
||||
bulletinId: string; // Bulletin identifier (BLN#)
|
||||
text: string;
|
||||
group?: string; // Optional group bulletin
|
||||
group?: string; // Optional group bulletin
|
||||
}
|
||||
|
||||
// Object Payload
|
||||
export interface ObjectPayload {
|
||||
type: 'object';
|
||||
name: string; // 9 character object name
|
||||
type: "object";
|
||||
name: string; // 9 character object name
|
||||
timestamp: ITimestamp;
|
||||
alive: boolean; // True if object is active, false if killed
|
||||
alive: boolean; // True if object is active, false if killed
|
||||
position: IPosition;
|
||||
course?: number;
|
||||
speed?: number;
|
||||
@@ -168,15 +169,15 @@ export interface ObjectPayload {
|
||||
|
||||
// Item Payload
|
||||
export interface ItemPayload {
|
||||
type: 'item';
|
||||
name: string; // 3-9 character item name
|
||||
alive: boolean; // True if item is active, false if killed
|
||||
type: "item";
|
||||
name: string; // 3-9 character item name
|
||||
alive: boolean; // True if item is active, false if killed
|
||||
position: IPosition;
|
||||
}
|
||||
|
||||
// Status Payload
|
||||
export interface StatusPayload {
|
||||
type: 'status';
|
||||
type: "status";
|
||||
timestamp?: ITimestamp;
|
||||
text: string;
|
||||
maidenhead?: string; // Optional Maidenhead grid locator
|
||||
@@ -188,106 +189,106 @@ export interface StatusPayload {
|
||||
|
||||
// Query Payload
|
||||
export interface QueryPayload {
|
||||
type: 'query';
|
||||
queryType: string; // e.g., 'APRSD', 'APRST', 'PING'
|
||||
target?: string; // Target callsign or area
|
||||
type: "query";
|
||||
queryType: string; // e.g., 'APRSD', 'APRST', 'PING'
|
||||
target?: string; // Target callsign or area
|
||||
}
|
||||
|
||||
// Telemetry Data Payload
|
||||
export interface TelemetryDataPayload {
|
||||
type: 'telemetry-data';
|
||||
type: "telemetry-data";
|
||||
sequence: number;
|
||||
analog: number[]; // Up to 5 analog channels
|
||||
digital: number; // 8-bit digital value
|
||||
analog: number[]; // Up to 5 analog channels
|
||||
digital: number; // 8-bit digital value
|
||||
}
|
||||
|
||||
// Telemetry Parameter Names
|
||||
export interface TelemetryParameterPayload {
|
||||
type: 'telemetry-parameters';
|
||||
names: string[]; // Parameter names
|
||||
type: "telemetry-parameters";
|
||||
names: string[]; // Parameter names
|
||||
}
|
||||
|
||||
// Telemetry Unit/Label
|
||||
export interface TelemetryUnitPayload {
|
||||
type: 'telemetry-units';
|
||||
units: string[]; // Units for each parameter
|
||||
type: "telemetry-units";
|
||||
units: string[]; // Units for each parameter
|
||||
}
|
||||
|
||||
// Telemetry Coefficients
|
||||
export interface TelemetryCoefficientsPayload {
|
||||
type: 'telemetry-coefficients';
|
||||
type: "telemetry-coefficients";
|
||||
coefficients: {
|
||||
a: number[]; // a coefficients
|
||||
b: number[]; // b coefficients
|
||||
c: number[]; // c coefficients
|
||||
a: number[]; // a coefficients
|
||||
b: number[]; // b coefficients
|
||||
c: number[]; // c coefficients
|
||||
};
|
||||
}
|
||||
|
||||
// Telemetry Bit Sense/Project Name
|
||||
export interface TelemetryBitSensePayload {
|
||||
type: 'telemetry-bitsense';
|
||||
sense: number; // 8-bit sense value
|
||||
type: "telemetry-bitsense";
|
||||
sense: number; // 8-bit sense value
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
// Weather Report Payload
|
||||
export interface WeatherPayload {
|
||||
type: 'weather';
|
||||
timestamp?: ITimestamp;
|
||||
position?: IPosition;
|
||||
windDirection?: number; // Degrees
|
||||
windSpeed?: number; // MPH
|
||||
windGust?: number; // MPH
|
||||
temperature?: number; // Fahrenheit
|
||||
rainLastHour?: number; // Hundredths of inch
|
||||
rainLast24Hours?: number; // Hundredths of inch
|
||||
rainSinceMidnight?: number; // Hundredths of inch
|
||||
humidity?: number; // Percent
|
||||
pressure?: number; // Tenths of millibar
|
||||
luminosity?: number; // Watts per square meter
|
||||
snowfall?: number; // Inches
|
||||
rawRain?: number; // Raw rain counter
|
||||
software?: string; // Weather software type
|
||||
weatherUnit?: string; // Weather station type
|
||||
type: "weather";
|
||||
timestamp?: ITimestamp;
|
||||
position?: IPosition;
|
||||
windDirection?: number; // Degrees
|
||||
windSpeed?: number; // MPH
|
||||
windGust?: number; // MPH
|
||||
temperature?: number; // Fahrenheit
|
||||
rainLastHour?: number; // Hundredths of inch
|
||||
rainLast24Hours?: number; // Hundredths of inch
|
||||
rainSinceMidnight?: number; // Hundredths of inch
|
||||
humidity?: number; // Percent
|
||||
pressure?: number; // Tenths of millibar
|
||||
luminosity?: number; // Watts per square meter
|
||||
snowfall?: number; // Inches
|
||||
rawRain?: number; // Raw rain counter
|
||||
software?: string; // Weather software type
|
||||
weatherUnit?: string; // Weather station type
|
||||
}
|
||||
|
||||
// Raw GPS Payload (NMEA sentences)
|
||||
export interface RawGPSPayload {
|
||||
type: 'raw-gps';
|
||||
sentence: string; // Raw NMEA sentence
|
||||
type: "raw-gps";
|
||||
sentence: string; // Raw NMEA sentence
|
||||
}
|
||||
|
||||
// Station Capabilities Payload
|
||||
export interface StationCapabilitiesPayload {
|
||||
type: 'capabilities';
|
||||
type: "capabilities";
|
||||
capabilities: string[];
|
||||
}
|
||||
|
||||
// User-Defined Payload
|
||||
export interface UserDefinedPayload {
|
||||
type: 'user-defined';
|
||||
type: "user-defined";
|
||||
userPacketType: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
// Third-Party Traffic Payload
|
||||
export interface ThirdPartyPayload {
|
||||
type: 'third-party';
|
||||
header: string; // Source path of third-party packet
|
||||
payload: string; // Nested APRS packet
|
||||
type: "third-party";
|
||||
header: string; // Source path of third-party packet
|
||||
payload: string; // Nested APRS packet
|
||||
}
|
||||
|
||||
// DF Report Payload
|
||||
export interface DFReportPayload {
|
||||
type: 'df-report';
|
||||
type: "df-report";
|
||||
timestamp?: ITimestamp;
|
||||
position: IPosition;
|
||||
course?: number;
|
||||
bearing?: number; // Direction finding bearing
|
||||
quality?: number; // Signal quality
|
||||
strength?: number; // Signal strength
|
||||
height?: number; // Antenna height
|
||||
gain?: number; // Antenna gain
|
||||
bearing?: number; // Direction finding bearing
|
||||
quality?: number; // Signal quality
|
||||
strength?: number; // Signal strength
|
||||
height?: number; // Antenna height
|
||||
gain?: number; // Antenna gain
|
||||
directivity?: string; // Antenna directivity pattern
|
||||
}
|
||||
|
||||
@@ -296,30 +297,31 @@ export interface BasePayload {
|
||||
}
|
||||
|
||||
// Union type for all decoded payload types
|
||||
export type Payload = BasePayload & (
|
||||
| PositionPayload
|
||||
| MicEPayload
|
||||
| MessagePayload
|
||||
| BulletinPayload
|
||||
| ObjectPayload
|
||||
| ItemPayload
|
||||
| StatusPayload
|
||||
| QueryPayload
|
||||
| TelemetryDataPayload
|
||||
| TelemetryParameterPayload
|
||||
| TelemetryUnitPayload
|
||||
| TelemetryCoefficientsPayload
|
||||
| TelemetryBitSensePayload
|
||||
| WeatherPayload
|
||||
| RawGPSPayload
|
||||
| StationCapabilitiesPayload
|
||||
| UserDefinedPayload
|
||||
| ThirdPartyPayload
|
||||
| DFReportPayload
|
||||
);
|
||||
export type Payload = BasePayload &
|
||||
(
|
||||
| PositionPayload
|
||||
| MicEPayload
|
||||
| MessagePayload
|
||||
| BulletinPayload
|
||||
| ObjectPayload
|
||||
| ItemPayload
|
||||
| StatusPayload
|
||||
| QueryPayload
|
||||
| TelemetryDataPayload
|
||||
| TelemetryParameterPayload
|
||||
| TelemetryUnitPayload
|
||||
| TelemetryCoefficientsPayload
|
||||
| TelemetryBitSensePayload
|
||||
| WeatherPayload
|
||||
| RawGPSPayload
|
||||
| StationCapabilitiesPayload
|
||||
| UserDefinedPayload
|
||||
| ThirdPartyPayload
|
||||
| DFReportPayload
|
||||
);
|
||||
|
||||
// Extended Frame with decoded payload
|
||||
export interface DecodedFrame extends IFrame {
|
||||
decoded?: Payload;
|
||||
decoded?: Payload;
|
||||
structure?: PacketStructure; // Routing and other frame-level sections
|
||||
}
|
||||
|
||||
19
src/index.ts
19
src/index.ts
@@ -1,14 +1,6 @@
|
||||
export {
|
||||
Frame,
|
||||
Address,
|
||||
Timestamp,
|
||||
} from "./frame";
|
||||
export { Frame, Address, Timestamp } from "./frame";
|
||||
|
||||
export {
|
||||
type IAddress,
|
||||
type IFrame,
|
||||
DataTypeIdentifier,
|
||||
} from "./frame.types";
|
||||
export { type IAddress, type IFrame, DataTypeIdentifier } from "./frame.types";
|
||||
|
||||
export {
|
||||
type ISymbol,
|
||||
@@ -48,10 +40,3 @@ export {
|
||||
celsiusToFahrenheit,
|
||||
fahrenheitToCelsius,
|
||||
} from "./parser";
|
||||
export {
|
||||
type PacketStructure,
|
||||
type PacketSegment,
|
||||
type PacketField,
|
||||
type PacketFieldBit,
|
||||
FieldType,
|
||||
} from "./parser.types";
|
||||
|
||||
@@ -15,14 +15,16 @@ export const base91ToNumber = (str: string): number => {
|
||||
const digit = charCode - 33; // Base91 uses chars 33-123 (! to {)
|
||||
|
||||
if (digit < 0 || digit >= base) {
|
||||
throw new Error(`Invalid Base91 character: '${str[i]}' (code ${charCode})`);
|
||||
throw new Error(
|
||||
`Invalid Base91 character: '${str[i]}' (code ${charCode})`,
|
||||
);
|
||||
}
|
||||
|
||||
value = value * base + digit;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/* Conversions from Freedom Units to whatever the rest of the world uses and understands. */
|
||||
|
||||
@@ -38,7 +40,7 @@ const FAHRENHEIT_TO_CELSIUS_OFFSET = 32;
|
||||
*/
|
||||
export const knotsToKmh = (knots: number): number => {
|
||||
return knots * KNOTS_TO_KMH;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert speed from kilometers per hour to knots.
|
||||
@@ -48,7 +50,7 @@ export const knotsToKmh = (knots: number): number => {
|
||||
*/
|
||||
export const kmhToKnots = (kmh: number): number => {
|
||||
return kmh / KNOTS_TO_KMH;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert altitude from feet to meters.
|
||||
@@ -58,7 +60,7 @@ export const kmhToKnots = (kmh: number): number => {
|
||||
*/
|
||||
export const feetToMeters = (feet: number): number => {
|
||||
return feet * FEET_TO_METERS;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert altitude from meters to feet.
|
||||
@@ -68,7 +70,7 @@ export const feetToMeters = (feet: number): number => {
|
||||
*/
|
||||
export const metersToFeet = (meters: number): number => {
|
||||
return meters / FEET_TO_METERS;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert temperature from Celsius to Fahrenheit.
|
||||
@@ -77,8 +79,8 @@ export const metersToFeet = (meters: number): number => {
|
||||
* @returns equivalent temperature in Fahrenheit
|
||||
*/
|
||||
export const celsiusToFahrenheit = (celsius: number): number => {
|
||||
return (celsius * 9/5) + FAHRENHEIT_TO_CELSIUS_OFFSET;
|
||||
}
|
||||
return (celsius * 9) / 5 + FAHRENHEIT_TO_CELSIUS_OFFSET;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert temperature from Fahrenheit to Celsius.
|
||||
@@ -87,5 +89,5 @@ export const celsiusToFahrenheit = (celsius: number): number => {
|
||||
* @returns equivalent temperature in Celsius
|
||||
*/
|
||||
export const fahrenheitToCelsius = (fahrenheit: number): number => {
|
||||
return (fahrenheit - FAHRENHEIT_TO_CELSIUS_OFFSET) * 5/9;
|
||||
}
|
||||
return ((fahrenheit - FAHRENHEIT_TO_CELSIUS_OFFSET) * 5) / 9;
|
||||
};
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
export enum FieldType {
|
||||
BITS = 0,
|
||||
UINT8 = 1,
|
||||
UINT16_LE = 2,
|
||||
UINT16_BE = 3,
|
||||
UINT32_LE = 4,
|
||||
UINT32_BE = 5,
|
||||
BYTES = 6, // 8-bits per value
|
||||
WORDS = 7, // 16-bits per value
|
||||
DWORDS = 8, // 32-bits per value
|
||||
QWORDS = 9, // 64-bits per value
|
||||
STRING = 10,
|
||||
C_STRING = 11, // Null-terminated string
|
||||
CHAR = 12, // Single ASCII character
|
||||
}
|
||||
|
||||
// Interface for the parsed packet segments, used for debugging and testing.
|
||||
export type PacketStructure = PacketSegment[];
|
||||
|
||||
export interface PacketSegment {
|
||||
name: string;
|
||||
data: Uint8Array;
|
||||
fields: PacketField[];
|
||||
}
|
||||
|
||||
export interface PacketField {
|
||||
type: FieldType;
|
||||
size: number; // Size in bytes
|
||||
name?: string;
|
||||
bits?: PacketFieldBit[]; // Only for bit fields in FieldType.BITS
|
||||
value?: any; // Optional decoded value
|
||||
}
|
||||
|
||||
export interface PacketFieldBit {
|
||||
name: string;
|
||||
size: number; // Size in bits
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { IPosition, ISymbol } from "./frame.types";
|
||||
|
||||
export class Symbol implements ISymbol {
|
||||
table: string; // Symbol table identifier
|
||||
code: string; // Symbol code
|
||||
table: string; // Symbol table identifier
|
||||
code: string; // Symbol code
|
||||
|
||||
constructor(table: string, code?: string) {
|
||||
if (code === undefined) {
|
||||
@@ -10,7 +10,9 @@ export class Symbol implements ISymbol {
|
||||
this.code = table[1];
|
||||
this.table = table[0];
|
||||
} else {
|
||||
throw new Error(`Invalid symbol format: '${table}' (expected 2 characters if code is not provided)`);
|
||||
throw new Error(
|
||||
`Invalid symbol format: '${table}' (expected 2 characters if code is not provided)`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.table = table;
|
||||
@@ -24,12 +26,12 @@ export class Symbol implements ISymbol {
|
||||
}
|
||||
|
||||
export class Position implements IPosition {
|
||||
latitude: number; // Decimal degrees
|
||||
longitude: number; // Decimal degrees
|
||||
latitude: number; // Decimal degrees
|
||||
longitude: number; // Decimal degrees
|
||||
ambiguity?: number; // Position ambiguity (0-4)
|
||||
altitude?: number; // Meters
|
||||
speed?: number; // Speed in knots/kmh depending on source
|
||||
course?: number; // Course in degrees
|
||||
altitude?: number; // Meters
|
||||
speed?: number; // Speed in knots/kmh depending on source
|
||||
course?: number; // Course in degrees
|
||||
symbol?: Symbol;
|
||||
comment?: string;
|
||||
|
||||
@@ -40,7 +42,7 @@ export class Position implements IPosition {
|
||||
this.altitude = data.altitude;
|
||||
this.speed = data.speed;
|
||||
this.course = data.course;
|
||||
if (typeof data.symbol === 'string') {
|
||||
if (typeof data.symbol === "string") {
|
||||
this.symbol = new Symbol(data.symbol);
|
||||
} else if (data.symbol) {
|
||||
this.symbol = new Symbol(data.symbol.table, data.symbol.code);
|
||||
@@ -51,21 +53,21 @@ export class Position implements IPosition {
|
||||
public toString(): string {
|
||||
const latStr = this.latitude.toFixed(5);
|
||||
const lonStr = this.longitude.toFixed(5);
|
||||
const altStr = this.altitude !== undefined ? `,${this.altitude}m` : '';
|
||||
const altStr = this.altitude !== undefined ? `,${this.altitude}m` : "";
|
||||
return `${latStr},${lonStr}${altStr}`;
|
||||
}
|
||||
|
||||
public distanceTo(other: IPosition): number {
|
||||
const R = 6371e3; // Earth radius in meters
|
||||
const lat1 = this.latitude * Math.PI / 180;
|
||||
const lat2 = other.latitude * Math.PI / 180;
|
||||
const dLat = (other.latitude - this.latitude) * Math.PI / 180;
|
||||
const dLon = (other.longitude - this.longitude) * Math.PI / 180;
|
||||
const lat1 = (this.latitude * Math.PI) / 180;
|
||||
const lat2 = (other.latitude * Math.PI) / 180;
|
||||
const dLat = ((other.latitude - this.latitude) * Math.PI) / 180;
|
||||
const dLon = ((other.longitude - this.longitude) * Math.PI) / 180;
|
||||
|
||||
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
|
||||
Math.cos(lat1) * Math.cos(lat2) *
|
||||
Math.sin(dLon/2) * Math.sin(dLon/2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
||||
const a =
|
||||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
return R * c; // Distance in meters
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user