Tim,
I've had a quick look at your code and although I've not tried it, I do wonder if your method for looking for an existing graphic is going to work very quickly and if it will scale to lots of graphics. I can see how your use of the streams works, but I do think a quicker way to get to your graphics would help.
Your architecture diagram makes sense and I've seen plenty of implementations like this. In my experience, the UDP feed with be a stream of messages giving updates for items where each item has a unique identifier. This unique identifier looks like ICAO in your implementation.
So your workflow would be something like:
- An update comes in for item XYZ
- Check to see if there is a graphic for XYZ. If there is update it, if not add a new one.
So in your app you need a super efficient way of getting the graphic associated with XYZ.
Searching in the graphics then in the attributes isn't going to be quick if you've got lots of graphics, so I would use a HashMap like this:
private HashMap<String, Graphic> aircraftList;
The key of this can be used for your unique reference and its very fast to look this up and get the graphic. I've thrown together a crude app to show how this might work. The app gradually adds up to 10000 graphics and updates all the graphics every 20ms by getting hold of the graphic via the HashMap.
package com.esri.samples.display_map;
import com.esri.arcgisruntime.geometry.Point;
import com.esri.arcgisruntime.mapping.Basemap;
import com.esri.arcgisruntime.mapping.view.Graphic;
import com.esri.arcgisruntime.mapping.view.GraphicsOverlay;
import com.esri.arcgisruntime.symbology.SimpleMarkerSymbol;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import com.esri.arcgisruntime.mapping.ArcGISMap;
import com.esri.arcgisruntime.mapping.view.MapView;
import java.util.*;
public class GraphicUpdating extends Application {
private MapView mapView;
private GraphicsOverlay graphicsOverlay;
private SimpleMarkerSymbol markerSymbol;
private HashMap<String, Graphic> aircraftList;
private Timer timer;
@Override
public void start(Stage stage) {
try {
// create stack pane and application scene
BorderPane stackPane = new BorderPane();
Scene scene = new Scene(stackPane);
// set title, size, and add scene to stage
stage.setTitle("Graphic updating sample");
stage.setWidth(800);
stage.setHeight(700);
stage.setScene(scene);
stage.show();
// authentication with an API key or named user is required to access basemaps and other location services
String yourAPIKey = System.getProperty("apiKey");
//ArcGISRuntimeEnvironment.setApiKey(yourAPIKey);
// create a map with the standard imagery basemap style
ArcGISMap map = new ArcGISMap(Basemap.createOpenStreetMap());
// create a map view and set the map to it
mapView = new MapView();
mapView.setMap(map);
HBox hBox = new HBox();
stackPane.setTop(hBox);
stackPane.setCenter(mapView);
//symbols to be used
markerSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbol.Style.CIRCLE, 0xFF00FF00, 10);
//graphics overlay
graphicsOverlay = new GraphicsOverlay();
mapView.getGraphicsOverlays().add(graphicsOverlay);
//start timer for adding and moving graphics
StartGraphicController(10000);
} catch (Exception e) {
// on any error, display the stack trace.
e.printStackTrace();
}
}
private class Updater extends TimerTask {
private int maxGraphics;
private int numGraphics = 0;
private int updates = 0;
private Random random = new Random();
@Override
public void run() {
// add a new graphic after every 5 updates
if (updates++ == 4) {
updates = 0;
//check we've not reached the max
if (numGraphics < maxGraphics) {
//System.out.println("adding new");
numGraphics++;
// create a new graphic
Point pt = new Point(random.nextDouble() * 5000000, random.nextDouble() * 5000000);
Graphic graphic = new Graphic(pt, markerSymbol);
graphicsOverlay.getGraphics().add(graphic);
UUID guid = UUID.randomUUID();
// add it to the hashmap
aircraftList.put(guid.toString(), graphic);
}
}
// loop through all aircraft and update them
for (String id : aircraftList.keySet()) {
MoveAircraft(id);
}
}
//constructor
public Updater(int maxGraphics) {
this.maxGraphics = maxGraphics;
System.out.println("constructor");
}
}
private void MoveAircraft(String id) {
//get graphic from the id
Graphic graphic = aircraftList.get(id);
// read current position
Point pos = (Point) graphic.getGeometry();
// new graphic
Point newPos = new Point(pos.getX() + 1000, pos.getY());
graphic.setGeometry(newPos);
}
private void StartGraphicController(int maxGraphics) {
aircraftList = new HashMap<>();
timer = new Timer();
Updater updater = new Updater(maxGraphics);
timer.schedule(updater,1000,20);
}
/**
* Stops and releases all resources used in application.
*/
@Override
public void stop() {
if (mapView != null) {
mapView.dispose();
}
timer.cancel();
}
/**
* Opens and runs application.
*
* @param args arguments passed to this application
*/
public static void main(String[] args) {
Application.launch(args);
}
}
It's not a great bit of code, but you'll see its very fast to update and the UI remains responsive to panning and zooming whilst its updating up to 10000 graphics every 20ms.