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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# New York Exchange Format (NXF)
___
[![](https://jitpack.io/v/io.wtmsb/NXF-Java.svg)](https://jitpack.io/#io.wtmsb/NXF-Java)

A new mechanism for exchanging (simulated) air traffic radar data using [Protocol Buffers](https://developers.google.com/protocol-buffers).

## How to use
### Java
Note: use `1.0-dev-SNAPSHOT` as the tag in your application
Requires: Java 11

Development branch is subject to constant change.
* Using jitpack: follow instructions on https://jitpack.io/#io.wtmsb/NXF-Java

* Using Maven Local: download project and run gradle task `publishToMavenLocal`
* Use tag `1.0-dev-SNAPSHOT` in your app.
21 changes: 19 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,29 @@ sourceSets {

dependencies {
implementation 'com.github.jitpack:gradle-simple:1.1'
implementation 'com.google.protobuf:protobuf-java:3.20.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
implementation 'com.google.protobuf:protobuf-java:3.21.1'
implementation 'org.locationtech.spatial4j:spatial4j:0.8'
implementation 'com.google.guava:guava:31.1-jre'
implementation 'org.springframework:spring-core:5.3.20'
implementation 'org.springframework:spring-context:5.3.20'
implementation 'org.hibernate.validator:hibernate-validator:6.2.3.Final'
implementation 'org.hibernate.validator:hibernate-validator-cdi:6.2.3.Final'
implementation 'org.glassfish:jakarta.el:3.0.3'
implementation 'jakarta.validation:jakarta.validation-api:2.0.2'

implementation 'org.slf4j:jul-to-slf4j:1.7.36'
implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.18.0'
implementation 'ch.qos.logback:logback-classic:1.2.11'

testImplementation platform('org.junit:junit-bom:5.8.2')
testImplementation 'org.junit.jupiter:junit-jupiter'
}

test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}

afterEvaluate {
Expand Down
71 changes: 71 additions & 0 deletions src/main/java/Program.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import com.google.common.collect.ListMultimap;
import com.google.protobuf.ByteString;
import io.wtmsb.nxf.domain.RadarTarget;
import io.wtmsb.nxf.domain.Track;
import io.wtmsb.nxf.manager.TrackManager;
import io.wtmsb.nxf.message.radar.NxfRadar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Component;

import java.util.function.BiConsumer;

@Component
@ComponentScan(basePackages = "io.wtmsb.nxf.manager")
public class Program {
@Autowired
private TrackManager manager;

public static void main(String[] args) {
try (GenericApplicationContext ctx = new AnnotationConfigApplicationContext(Program.class)) {
var bean = ctx.getBean(Program.class);
bean.run();
}
}

public void run() {
for (int i = 0; i < 10; i++) {
RadarTarget rt1 = new RadarTarget(
NxfRadar.RadarTarget.newBuilder()
.setReportedAltitude(10000)
.setBeaconCode(ByteString.fromHex("0480"))
.setReturnTime(i)
.build()
);
RadarTarget rt2 = new RadarTarget(
NxfRadar.RadarTarget.newBuilder()
.setReportedAltitude(12000)
.setBeaconCode(ByteString.fromHex("010481")) // should fail
.setReturnTime(i + 10)
.build()
);

manager.addTarget(rt1, "N1");
manager.addTarget(rt2, "N2");
}

System.out.println("Showing all RadarTarget->Track...");
manager.getTargetTrackMap().forEach((radarTarget, track) -> {
System.out.println("radarTarget.getBeaconCode: " + radarTarget.getBeaconCode());
System.out.println(radarTarget.getReturnTime().toEpochMilli() / 1000 + ": " + track.getFlightData().getCallsign());
}
);

System.out.println("Showing all Track->List<RadarTarget>...");
ListMultimap<Track, RadarTarget> trackRadarTargetListMultiMap = manager.getTrackRadarTargetMultiMap();
trackRadarTargetListMultiMap.asMap().forEach((track, radarTargetList) -> {
System.out.print(track.getFlightData().getCallsign() + ": ");
for (RadarTarget radarTarget : radarTargetList) {
System.out.print(radarTarget.getReturnTime().toEpochMilli() / 1000 + " ");
}
System.out.println();
});

System.out.println("Showing all String->Track...");
manager.getTrackByCallsignMap().forEach((s, track) ->
System.out.println(s + ": " + track.getFlightData().getCallsign())
);
}
}
23 changes: 23 additions & 0 deletions src/main/java/io/wtmsb/nxf/domain/ControllingUnit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.wtmsb.nxf.domain;

import io.wtmsb.nxf.manager.ControllerManager;
import io.wtmsb.nxf.message.radar.NxfRadar;
import io.wtmsb.nxf.validation.AlphanumericString;
import lombok.*;

/**
* Immutable, shared object managed by {@link ControllerManager}
*/
@Getter @AllArgsConstructor @EqualsAndHashCode
public final class ControllingUnit {
@NonNull @AlphanumericString(maxLength = 3)
private final String facility;

@NonNull @AlphanumericString(maxLength = 3)
private final String sector;

public ControllingUnit(NxfRadar.ControllingUnit cuMessage) {
facility = cuMessage.getFacility();
sector = cuMessage.getSector();
}
}
95 changes: 95 additions & 0 deletions src/main/java/io/wtmsb/nxf/domain/FlightData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package io.wtmsb.nxf.domain;

import com.google.protobuf.ByteString;
import io.wtmsb.nxf.manager.ControllerManager;
import io.wtmsb.nxf.message.radar.NxfRadar;
import io.wtmsb.nxf.validation.*;
import lombok.*;
import org.springframework.util.StringUtils;

import javax.validation.constraints.Max;
import javax.validation.constraints.PastOrPresent;
import java.time.Instant;

/**
* Mutable object managed by {@link io.wtmsb.nxf.manager.TrackManager}
*/
@Getter @Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class FlightData implements IRadarComponent {
@Callsign
@EqualsAndHashCode.Include
private String callsign;

@EqualsAndHashCode.Include
private boolean hasIcaoAddress = false;

@IcaoAddress
@EqualsAndHashCode.Include
private ByteString icaoAddress = DEFAULT_ICAO_ADDRESS;

@EqualsAndHashCode.Include
private boolean hasAssignedBeaconCode = false;

@BeaconCode
@EqualsAndHashCode.Include
private ByteString assignedBeaconCode = DEFAULT_BEACON_CODE;

@NonNull @PastOrPresent
@EqualsAndHashCode.Include
private Instant lastUpdated = Instant.now();

@AlphanumericString(maxLength = 4)
private String aircraftType = "";

@AlphanumericString(maxLength = 1)
private String equipmentSuffix = "";

@NonNull
private FlightRule flightRule = FlightRule.INSTRUMENT;

@AlphanumericString(maxLength = 4)
private String departurePoint = "";

@AlphanumericString(maxLength = 4)
private String destination = "";

@NonNull @Max(MAX_ALTITUDE)
private Integer requestedAltitude = 0;

@RouteString(maxLength = 2000)
private String routeString = "";

@NonNull
private ControllingUnit currentController = ControllerManager.getUncontrolledUnit();

@NonNull
private ControllingUnit nextController = ControllerManager.getUncontrolledUnit();

@NonNull @Max(MAX_ALTITUDE)
private Integer assignedTemporaryAltitude = 0;

@NonNull @Max(MAX_ALTITUDE)
private Integer assignedFinalAltitude = 0;

@NonNull
private FlightDataSupplement supplement = FlightDataSupplement.getDefault();

public FlightData(@Callsign String _callsign) {
callsign = _callsign;
}

public FlightData(NxfRadar.FlightData fDataMessage) {
this(fDataMessage.getIdentification().getCallsign());
if (fDataMessage.getIdentification().hasIcaoAddress()) {
icaoAddress = fDataMessage.getIdentification().getIcaoAddress();
}
assignedBeaconCode = fDataMessage.getAssignedBeaconCode();
flightRule = IRadarComponent.getFlightRuleOrDefault(fDataMessage.getFlightRuleValue());

aircraftType = fDataMessage.getAircraftType();
departurePoint = fDataMessage.getDestination();
requestedAltitude = fDataMessage.getRequestedAltitude();
routeString = StringUtils.trimWhitespace(fDataMessage.getRouteString());
}
}
57 changes: 57 additions & 0 deletions src/main/java/io/wtmsb/nxf/domain/FlightDataSupplement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.wtmsb.nxf.domain;

import io.wtmsb.nxf.message.radar.NxfRadar;
import io.wtmsb.nxf.validation.RouteString;
import lombok.*;

import javax.validation.constraints.Max;

import static io.wtmsb.nxf.domain.IRadarComponent.*;

@Getter @Setter @EqualsAndHashCode
public final class FlightDataSupplement {
private static final FlightDataSupplement DEFAULT = new FlightDataSupplement();

@NonNull @Max(MAX_ALTITUDE)
private Integer assignedTemporaryAltitude;

@NonNull @Max(MAX_ALTITUDE)
private Integer assignedFinalAltitude;

@NonNull @RouteString(maxLength = 5)
private String pad1;

@NonNull @RouteString(maxLength = 5)
private String pad2;

@NonNull @RouteString(maxLength = 3)
private String runway;

@NonNull @RouteString(maxLength = 5)
private String exitFix;

@NonNull
private LeaderLineDirection leaderLineDirection;

private FlightDataSupplement() {
this(NxfRadar.FlightDataSupplement.getDefaultInstance());
}

public FlightDataSupplement(NxfRadar.FlightDataSupplement supplementMessage) {
assignedTemporaryAltitude = supplementMessage.getAssignedTemporaryAltitude();
assignedFinalAltitude = supplementMessage.getAssignedFinalAltitude();
pad1 = supplementMessage.getPad1();
pad2 = supplementMessage.getPad2();
runway = supplementMessage.getRunway();
exitFix = supplementMessage.getExitFix();
leaderLineDirection = getLeaderLineDirectionOrDefault(supplementMessage.getLeaderLineDirectionValue());
}

public boolean isDefault() {
return this.equals(DEFAULT);
}

public static FlightDataSupplement getDefault() {
return new FlightDataSupplement();
}
}
44 changes: 44 additions & 0 deletions src/main/java/io/wtmsb/nxf/domain/IRadarComponent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.wtmsb.nxf.domain;

import com.google.protobuf.ByteString;
import io.wtmsb.nxf.validation.IcaoAddress;

public interface IRadarComponent {
ByteString DEFAULT_BEACON_CODE = ByteString.fromHex("0000");
ByteString DEFAULT_ICAO_ADDRESS = ByteString.fromHex("000000");
int MAX_ALTITUDE = 100000;

enum TransponderMode {
PRIMARY, MODE_A, MODE_C, MODE_S
}

enum FlightRule {
INSTRUMENT, VISUAL, SPECIAL_VISUAL, DEFENSE_VISUAL
}

enum WakeCategory {
NO_WEIGHT, CAT_A, CAT_B, CAT_C, CAT_D, CAT_E, CAT_F
}

enum LeaderLineDirection {
DEFAULT,
NW, N, NE, W,
HIDE,
E, SW, S, SE
}

static FlightRule getFlightRuleOrDefault(int ordinal) {
return (0 < ordinal && ordinal < FlightRule.values().length) ?
FlightRule.values()[ordinal] : FlightRule.INSTRUMENT;
}

static WakeCategory getWakeCategoryOrDefault(int ordinal) {
return (0 < ordinal && ordinal < WakeCategory.values().length) ?
WakeCategory.values()[ordinal] : WakeCategory.NO_WEIGHT;
}

static LeaderLineDirection getLeaderLineDirectionOrDefault(int ordinal) {
return (0 < ordinal && ordinal < LeaderLineDirection.values().length) ?
LeaderLineDirection.values()[ordinal] : LeaderLineDirection.DEFAULT;
}
}
Loading