#2149 P25P1 adds L3Harris Talker GPS support (#2208)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2025-03-22 06:22:40 -04:00
committed by GitHub
parent dd6ddd59e9
commit 728a85c180
9 changed files with 407 additions and 118 deletions

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -64,6 +64,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HeaderData;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisReturnToControlChannel;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasComplete;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerGPSComplete;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaEmergencyAlarmActivation;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkComplete;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnitGPS;
@@ -326,6 +327,10 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
{
processTalkerAlias(talkerAlias);
}
else if(iMessage instanceof LinkControlWord lcw)
{
processLC(lcw, iMessage.getTimestamp(), false);
}
}
/**
@@ -2093,6 +2098,26 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
broadcast(event);
}
break;
case L3HARRIS_TALKER_GPS_COMPLETE:
if(lcw instanceof LCHarrisTalkerGPSComplete gps)
{
mTrafficChannelManager.processP1TrafficCurrentUser(getCurrentFrequency(), gps.getLocation(), timestamp, lcw.toString());
//We want to preserve the current TO/FROM and any other identifiers for the GPS event and add the GPS location
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
mic.update(gps.getIdentifiers());
PlottableDecodeEvent event = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, timestamp)
.location(gps.getGeoPosition())
.channel(getCurrentChannel())
.details(gps.getLocation().toString())
.end(timestamp)
.protocol(Protocol.APCO25)
.identifiers(mic)
.build();
broadcast(event);
}
break;
default:
// if(lcw.getVendor().isLoggable())
// {

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,6 +29,9 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.IExtendedSourceMes
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.HarrisTalkerAliasAssembler;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasBase;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerGPSBlock1;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerGPSBlock2;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerGPSComplete;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkerAliasAssembler;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSourceIDExtension;
import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU1Message;
@@ -68,6 +71,8 @@ public class P25P1MessageProcessor implements Listener<IMessage>
private HDUMessage mHeldHDUMessage;
private LDU1Message mHeldLDU1Message;
private LDU2Message mHeldLDU2Message;
private LCHarrisTalkerGPSBlock1 mHeldHarrisGPSBlock1;
private long mHeldHarrisGPSBlock1Timestamp;
/**
* Motorola talker alias assembler for link control header and data blocks.
@@ -139,6 +144,26 @@ public class P25P1MessageProcessor implements Listener<IMessage>
processSourceIDExtension(null);
dispatch(ldu1);
//Process Harris Talker GPS messages
if(lcw instanceof LCHarrisTalkerGPSBlock1 block1)
{
mHeldHarrisGPSBlock1 = block1;
mHeldHarrisGPSBlock1Timestamp = ldu1.getTimestamp();
}
else if(lcw instanceof LCHarrisTalkerGPSBlock2 block2 && mHeldHarrisGPSBlock1 != null)
{
//Ensure block 1 and block 2 are within 2 seconds of each other
if(Math.abs(ldu1.getTimestamp() - mHeldHarrisGPSBlock1Timestamp) < 2000)
{
LCHarrisTalkerGPSComplete gps = LCHarrisTalkerGPSComplete.create(mHeldHarrisGPSBlock1, block2,
ldu1.getTimestamp());
dispatch(gps);
}
mHeldHarrisGPSBlock1 = null;
mHeldHarrisGPSBlock1Timestamp = 0;
}
}
else if(message instanceof LDU2Message ldu2)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -106,8 +106,9 @@ public enum LinkControlOpcode
MOTOROLA_UNKNOWN("MOTOROLA UNKNOWN", -1),
L3HARRIS_RETURN_TO_CONTROL_CHANNEL("UNKNOWN OPCODE 10", 10),
L3HARRIS_UNKNOWN_2A("UNKNOWN OPCODE 42", 42),
L3HARRIS_UNKNOWN_2B("UNKNOWN OPCODE 43", 43),
L3HARRIS_TALKER_GPS_BLOCK1("TALKER GPS 1/2", 42),
L3HARRIS_TALKER_GPS_BLOCK2("TALKER GPS 2/2", 43),
L3HARRIS_TALKER_GPS_COMPLETE("TALKER GPS", -2),
L3HARRIS_TALKER_ALIAS_BLOCK_1("TALKER ALIAS BLOCK 1", 50),
L3HARRIS_TALKER_ALIAS_BLOCK_2("TALKER ALIAS BLOCK 2", 51),
L3HARRIS_TALKER_ALIAS_BLOCK_3("TALKER ALIAS BLOCK 3", 52),
@@ -151,8 +152,7 @@ public enum LinkControlOpcode
/**
* L3Harris Opcodes
*/
public static final EnumSet<LinkControlOpcode> L3HARRIS_OPCODES = EnumSet.of(L3HARRIS_RETURN_TO_CONTROL_CHANNEL,
L3HARRIS_UNKNOWN_2A, L3HARRIS_UNKNOWN_2B, L3HARRIS_TALKER_ALIAS_BLOCK_1, L3HARRIS_TALKER_ALIAS_BLOCK_2,
public static final EnumSet<LinkControlOpcode> L3HARRIS_OPCODES = EnumSet.of(L3HARRIS_RETURN_TO_CONTROL_CHANNEL, L3HARRIS_TALKER_GPS_BLOCK1, L3HARRIS_TALKER_GPS_BLOCK2, L3HARRIS_TALKER_ALIAS_BLOCK_1, L3HARRIS_TALKER_ALIAS_BLOCK_2,
L3HARRIS_TALKER_ALIAS_BLOCK_3, L3HARRIS_TALKER_ALIAS_BLOCK_4, L3HARRIS_UNKNOWN);
/**
@@ -221,9 +221,9 @@ public enum LinkControlOpcode
case 10:
return L3HARRIS_RETURN_TO_CONTROL_CHANNEL;
case 42:
return L3HARRIS_UNKNOWN_2A;
return L3HARRIS_TALKER_GPS_BLOCK1;
case 43:
return L3HARRIS_UNKNOWN_2B;
return L3HARRIS_TALKER_GPS_BLOCK2;
case 50:
return L3HARRIS_TALKER_ALIAS_BLOCK_1;
case 51:

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,8 +25,8 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisT
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasBlock2;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasBlock3;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasBlock4;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode42;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode43;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerGPSBlock1;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerGPSBlock2;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaEmergencyAlarmActivation;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaFailsoft;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupGroupDelete;
@@ -161,10 +161,10 @@ public class LinkControlWordFactory
case L3HARRIS_RETURN_TO_CONTROL_CHANNEL:
return new LCHarrisReturnToControlChannel(message);
case L3HARRIS_UNKNOWN_2A:
return new LCHarrisUnknownOpcode42(message);
case L3HARRIS_UNKNOWN_2B:
return new LCHarrisUnknownOpcode43(message);
case L3HARRIS_TALKER_GPS_BLOCK1:
return new LCHarrisTalkerGPSBlock1(message);
case L3HARRIS_TALKER_GPS_BLOCK2:
return new LCHarrisTalkerGPSBlock2(message);
case L3HARRIS_TALKER_ALIAS_BLOCK_1:
return new LCHarrisTalkerAliasBlock1(message);
case L3HARRIS_TALKER_ALIAS_BLOCK_2:

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,19 +26,16 @@ import java.util.Collections;
import java.util.List;
/**
* L3Harris Unknown Opcode 43 (0x2B)
* <p>
* Observed in a voice traffic channel carried by an LDU1. This was transmitted once following an Opcode 42 (0x2A)
* which is also unknown.
* L3Harris Talker GPS - Block 1/2 (Opcode:0x2A)
*/
public class LCHarrisUnknownOpcode43 extends LinkControlWord
public class LCHarrisTalkerGPSBlock1 extends LinkControlWord
{
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCHarrisUnknownOpcode43(CorrectedBinaryMessage message)
public LCHarrisTalkerGPSBlock1(CorrectedBinaryMessage message)
{
super(message);
}
@@ -58,7 +55,7 @@ public class LCHarrisUnknownOpcode43 extends LinkControlWord
}
else
{
sb.append("L3HARRIS UNKNOWN OPCODE 43 MSG:").append(getMessage().toHexString());
sb.append("L3HARRIS TALKER GPS BLOCK 1/2 MSG:").append(getMessage().toHexString());
}
return sb.toString();
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,19 +26,16 @@ import java.util.Collections;
import java.util.List;
/**
* L3Harris Unknown Opcode 42 (0x2A)
* <p>
* Observed in a voice traffic channel carried by an LDU1. This was transmitted once followed by an Opcode 43 (0x2B)
* which is also unknown.
* L3Harris Talker GPS - Block 2/2 (Opcode:0x2B)
*/
public class LCHarrisUnknownOpcode42 extends LinkControlWord
public class LCHarrisTalkerGPSBlock2 extends LinkControlWord
{
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCHarrisUnknownOpcode42(CorrectedBinaryMessage message)
public LCHarrisTalkerGPSBlock2(CorrectedBinaryMessage message)
{
super(message);
}
@@ -58,7 +55,7 @@ public class LCHarrisUnknownOpcode42 extends LinkControlWord
}
else
{
sb.append("L3HARRIS UNKNOWN OPCODE 42 MSG:").append(getMessage().toHexString());
sb.append("L3HARRIS TALKER GPS BLOCK 2/2 MSG:").append(getMessage().toHexString());
}
return sb.toString();
}

View File

@@ -0,0 +1,190 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.identifier.P25Location;
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlOpcode;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisGPS;
import io.github.dsheirer.module.decode.p25.reference.Vendor;
import io.github.dsheirer.protocol.Protocol;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
import org.jdesktop.swingx.mapviewer.GeoPosition;
/**
* L3Harris Talker GPS Complete. This message is assembled from FLC L3Harris Talker GPS Block 1 and Block 2.
*/
public class LCHarrisTalkerGPSComplete extends LinkControlWord implements IMessage
{
private static final DecimalFormat GPS_FORMAT = new DecimalFormat("0.000000");
private static final SimpleDateFormat SDF = new SimpleDateFormat("HH:mm:ss");
static {
SDF.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private P25Location mLocation;
private GeoPosition mGeoPosition;
private List<Identifier> mIdentifiers;
private long mTimestamp;
/**
* Constructs a Link Control Word from the binary message sequence.
* @param message constructed from block 1 and block 2 fragments
*/
private LCHarrisTalkerGPSComplete(CorrectedBinaryMessage message, long timestamp)
{
super(message);
mTimestamp = timestamp;
}
@Override
public Protocol getProtocol()
{
return Protocol.APCO25;
}
@Override
public long getTimestamp()
{
return mTimestamp;
}
@Override
public int getTimeslot()
{
return P25P1Message.TIMESLOT_0;
}
/**
* Constructs an instance of this message from block 1 and block 2 FLC GPS message fragments.
* @param block1 FLC Harris Talker GPS
* @param block2 FLC Harris Talker GPS
* @param timestamp for the message
* @return instance of this message.
*/
public static LCHarrisTalkerGPSComplete create(LCHarrisTalkerGPSBlock1 block1, LCHarrisTalkerGPSBlock2 block2,
long timestamp)
{
CorrectedBinaryMessage cbm = new CorrectedBinaryMessage(112);
cbm.load(0, block1.getMessage().getSubMessage(16, 72));
cbm.load(56, block2.getMessage().getSubMessage(16, 72));
return new LCHarrisTalkerGPSComplete(cbm, timestamp);
}
/**
* Textual representation of this message
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("L3HARRIS TALKER GPS ");
GeoPosition geo = getGeoPosition();
sb.append(GPS_FORMAT.format(geo.getLatitude())).append(" ").append(GPS_FORMAT.format(geo.getLongitude()));
sb.append(" HEADING:").append(getHeading());
sb.append(" TIME:").append(SDF.format(getTimestampMs()));
sb.append(" UTC MSG:").append(getMessage().toHexString());
return sb.toString();
}
@Override
public Vendor getVendor()
{
return Vendor.HARRIS;
}
@Override
public LinkControlOpcode getOpcode()
{
return LinkControlOpcode.L3HARRIS_TALKER_GPS_COMPLETE;
}
@Override
public int getOpcodeNumber()
{
return getOpcode().getCode();
}
/**
* Heading
* @return heading, 0-359 degrees.
*/
public int getHeading()
{
return L3HarrisGPS.parseHeading(getMessage(), 0);
}
/**
* GPS Position time in milliseconds.
* @return time in ms UTC
*/
public long getTimestampMs()
{
return L3HarrisGPS.parseTimestamp(getMessage(), 0);
}
/**
* GPS Location
* @return location in decimal degrees
*/
public P25Location getLocation()
{
if(mLocation == null)
{
GeoPosition geoPosition = getGeoPosition();
mLocation = P25Location.createFrom(geoPosition.getLatitude(), geoPosition.getLongitude());
}
return mLocation;
}
/**
* Geo position
* @return position
*/
public GeoPosition getGeoPosition()
{
if(mGeoPosition == null)
{
mGeoPosition = new GeoPosition(L3HarrisGPS.parseLatitude(getMessage(), 0),
L3HarrisGPS.parseLongitude(getMessage(), 0));
}
return mGeoPosition;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getLocation());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,128 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.bits.IntField;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
/**
* Utility class to parse L3Harris GPS data from P25 Phase 1 and Phase 2 messages.
*/
public class L3HarrisGPS
{
public static final SimpleDateFormat SDF = new SimpleDateFormat("HH:mm:ss");
static {
SDF.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private static final DecimalFormat GPS_FORMAT = new DecimalFormat("0.000000");
//Bits 0 & 1 not set in sample data - seems unused for a 1/10000th of a minute number system
private static final IntField LAT_MINUTES_FRACTIONAL = IntField.range(0, 15);
private static final int LAT_HEMISPHERE = 16;
//Bit 39 & 41 not set in sample data
private static final IntField LAT_MINUTES = IntField.range(17, 23);
private static final IntField LAT_DEGREES = IntField.range(24, 31);
//Bits 56 & 57 not set in sample data - seems unused for a 1/5000th of a minute number system
private static final IntField LONG_MINUTES_FRACTIONAL = IntField.range(32, 47);
private static final int LONG_HEMISPHERE = 48;
//Bit 71 & 73 not set in sample data
private static final IntField LONG_MINUTES = IntField.range(49, 55);
private static final IntField LONG_DEGREES = IntField.range(56, 63);
//There's a leading bit missing from GPS Time to get from (2^16) to (2^17) needed address space (86,400 total seconds)
private static final IntField GPS_TIME = IntField.range(64, 79);
private static final int GPS_TIME_MSB = 80;
//This may not be accurate.
private static final IntField HEADING = IntField.range(95, 103);
/**
* Parses the latitude value from the message
* @param message containing latitude value.
* @param offset into the message to the start of the formatted GPS data.
* @return latitude in degrees decimal.
*/
public static double parseLatitude(CorrectedBinaryMessage message, int offset)
{
return parseCoordinate(message, offset, LAT_DEGREES, LAT_MINUTES, LAT_MINUTES_FRACTIONAL, LAT_HEMISPHERE);
}
/**
* Parses the longitude value from the message
* @param message containing longitude value.
* @param offset into the message to the start of the formatted GPS data.
* @return longitude in degrees decimal.
*/
public static double parseLongitude(CorrectedBinaryMessage message, int offset)
{
return parseCoordinate(message, offset, LONG_DEGREES, LONG_MINUTES, LONG_MINUTES_FRACTIONAL, LONG_HEMISPHERE);
}
/**
* Parses the latitude or longitude coordinate value from the message
* @param message containing the coordinate
* @param offset into the message to the start of the GPS data
* @param degrees field definition
* @param minutes field definition
* @param fractional field definition
* @param hemisphere field definition
* @return value in degrees decimal.
*/
private static double parseCoordinate(CorrectedBinaryMessage message, int offset, IntField degrees,
IntField minutes, IntField fractional, int hemisphere)
{
double longitude = message.getInt(degrees, offset);
longitude += ((message.getInt(minutes, offset) + (message.getInt(fractional, offset) / 10000.0)) / 60.0);
longitude *= (message.get(hemisphere + offset) ? -1 : 1);
return longitude;
}
/**
* Parse the timestamp
* @param message containing the timestamp
* @param offset into the message to the start of the GPS data.
* @return timestamp in milliseconds
*/
public static long parseTimestamp(CorrectedBinaryMessage message, int offset)
{
long seconds = message.getInt(GPS_TIME, offset);
if(message.get(offset + GPS_TIME_MSB))
{
seconds *= 2;
}
return seconds * 1000; //Convert seconds to milliseconds.
}
/**
* Heading
*
* @return heading, 0-359 degrees.
*/
public static int parseHeading(CorrectedBinaryMessage message, int offset)
{
return message.getInt(HEADING, offset);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.bits.IntField;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.identifier.P25Location;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor;
@@ -39,37 +38,12 @@ import org.jdesktop.swingx.mapviewer.GeoPosition;
*/
public class L3HarrisTalkerGpsLocation extends MacStructureVendor
{
public static final SimpleDateFormat SDF = new SimpleDateFormat("HH:mm:ss");
private static final DecimalFormat GPS_FORMAT = new DecimalFormat("0.000000");
private static final SimpleDateFormat SDF = new SimpleDateFormat("HH:mm:ss");
static {
SDF.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private static final DecimalFormat GPS_FORMAT = new DecimalFormat("0.000000");
private static final DecimalFormat FIXED = new DecimalFormat("000");
//Bits 24 & 25 not set in sample data - seems unused for a 1/5000th of a minute number system
private static final IntField LATITUDE_MINUTES_FRACTIONAL = IntField.range(24, 38);
private static final int LATITUDE_HEMISPHERE = 40;
//Bit 39 & 41 not set in sample data
private static final IntField LATITUDE_MINUTES = IntField.range(42, 47);
private static final IntField LATITUDE_DEGREES = IntField.range(48, 55);
//Bits 56 & 57 not set in sample data - seems unused for a 1/5000th of a minute number system
private static final IntField LONGITUDE_MINUTES_FRACTIONAL = IntField.range(56, 70);
private static final int LONGITUDE_HEMISPHERE = 72;
//Bit 71 & 73 not set in sample data
private static final IntField LONGITUDE_MINUTES = IntField.range(74, 79);
private static final IntField LONGITUDE_DEGREES = IntField.range(80, 87);
//There's a leading bit missing from GPS Time to get from (2^16) to (2^17) needed address space (86,400 total seconds)
private static final IntField GPS_TIME = IntField.range(88, 103);
private static final IntField U1 = IntField.length8(OCTET_14_BIT_104);
private static final IntField U2 = IntField.length8(OCTET_15_BIT_112);
private static final IntField U3 = IntField.length8(OCTET_16_BIT_120);
private static final IntField U4 = IntField.length8(OCTET_17_BIT_128);
//This may not be accurate.
private static final IntField HEADING = IntField.range(119, 127);
private static final int DATA_OFFSET = 24;
private P25Location mLocation;
private GeoPosition mGeoPosition;
@@ -102,10 +76,11 @@ public class L3HarrisTalkerGpsLocation extends MacStructureVendor
sb.append("VENDOR:").append(getVendor()).append(" TALKER GPS ");
}
sb.append(GPS_FORMAT.format(getLatitude())).append(" ").append(GPS_FORMAT.format(getLongitude()));
GeoPosition geo = getGeoPosition();
sb.append(GPS_FORMAT.format(geo.getLatitude())).append(" ").append(GPS_FORMAT.format(geo.getLongitude()));
sb.append(" HEADING:").append(getHeading());
sb.append(" TIME:").append(SDF.format(getTimestampMs()));
sb.append(" MSG:").append(getMessage().toHexString());
sb.append(" UTC MSG:").append(getMessage().toHexString());
return sb.toString();
}
@@ -115,7 +90,7 @@ public class L3HarrisTalkerGpsLocation extends MacStructureVendor
*/
public int getHeading()
{
return getInt(HEADING);
return L3HarrisGPS.parseHeading(getMessage(), getOffset() + DATA_OFFSET);
}
/**
@@ -124,7 +99,7 @@ public class L3HarrisTalkerGpsLocation extends MacStructureVendor
*/
public long getTimestampMs()
{
return getInt(GPS_TIME) * 1000; //Convert seconds to milliseconds.
return L3HarrisGPS.parseTimestamp(getMessage(), getOffset() + DATA_OFFSET);
}
/**
@@ -135,7 +110,8 @@ public class L3HarrisTalkerGpsLocation extends MacStructureVendor
{
if(mLocation == null)
{
mLocation = P25Location.createFrom(getLatitude(), getLongitude());
GeoPosition geoPosition = getGeoPosition();
mLocation = P25Location.createFrom(geoPosition.getLatitude(), geoPosition.getLongitude());
}
return mLocation;
@@ -149,62 +125,13 @@ public class L3HarrisTalkerGpsLocation extends MacStructureVendor
{
if(mGeoPosition == null)
{
mGeoPosition = new GeoPosition(getLatitude(), getLongitude());
mGeoPosition = new GeoPosition(L3HarrisGPS.parseLatitude(getMessage(), getOffset() + DATA_OFFSET),
L3HarrisGPS.parseLongitude(getMessage(), getOffset() + DATA_OFFSET));
}
return mGeoPosition;
}
/**
* GPS location - latitude
* @return latitude degrees decimal
*/
public double getLatitude()
{
return (getLatitudeDegrees() + (getLatitudeMinutes() / 60.0)) * (getMessage().get(LATITUDE_HEMISPHERE + getOffset()) ? -1 : 1);
}
/**
* Latitude degrees
*/
private double getLatitudeDegrees()
{
return getInt(LATITUDE_DEGREES);
}
/**
* Latitude minutes
*/
private double getLatitudeMinutes()
{
return getInt(LATITUDE_MINUTES) + getInt(LATITUDE_MINUTES_FRACTIONAL) / 5000d;
}
/**
* GPS location - longitude
* @return longitude degrees decimal
*/
public double getLongitude()
{
return (getLongitudeDegrees() + (getLongitudeMinutes() / 60.0)) * (getMessage().get(LONGITUDE_HEMISPHERE + getOffset()) ? -1 : 1);
}
/**
* Longitude degrees
*/
private double getLongitudeDegrees()
{
return getInt(LONGITUDE_DEGREES);
}
/**
* Longitude minutes
*/
private double getLongitudeMinutes()
{
return getInt(LONGITUDE_MINUTES) + getInt(LONGITUDE_MINUTES_FRACTIONAL) / 5000d;
}
@Override
public List<Identifier> getIdentifiers()
{