elastic / kibana

Your window into the Elastic Stack
https://www.elastic.co/products/kibana
Other
19.65k stars 8.23k forks source link

[Feature Request] Adding arrows to Kibana Track to show directionality #163669

Open petersadosky opened 1 year ago

petersadosky commented 1 year ago

Describe the feature:

Kibana v.8.9.0

Functionality to add an arrow to the end of each line segment in a Kibana Track layer to show directionality.

It is possible to do this currently by adding an additional "Top hits by entity" layer, and defining an "angle / degree" field in the index. The best way to get those angles though, at least that I'm aware of, is to query the existing index for the most recent record (filtered by whatever fields), and using the location field in that record to calculate the angle between that and the new incoming record. Downside is having to query the index every time a new record is coming in. And then also update the angle field for the most recent record with the calculated value.

Describe a specific use case for the feature:

Displaying vehicle routes on a map.

Thank you for your time! Would be great to have a built-in way to add directionality. Peter

elasticmachine commented 1 year ago

Pinging @elastic/kibana-presentation (Team:Presentation)

jsanz commented 4 months ago

Sharing here for reference a related workaround. If datasets are small a solution is to add two runtime fields in the Kibana Data View: midpoint and midbearing. The first returns the point in the middle of the line that connects origin and destination and the second returns the angle between the North, origin, and destination.

Then in Maps, on top of the Point to Point layer, a new cluster layer can be created using the midpoint geometry and adding a new metric with the average of midbearing. With that data definition, the symbol properties can be set to draw an arrow icon using the aggregated bearing as the direction.

[!NOTE] I think the visualization improves with the arrow in the middle but of course, the midpoint can be omitted and the arrow can be rendered at the end

image Sample map with the arrows drawn in the middle point

Dashboard Saved Object for the Kibana Flights sample dataset

An evident improvement is to compute these two fields at ingest with a pipeline and similar code so everything is ready for the rendering.

Runtime fields code ### `midpoint` This is a `geo_point` field in the middle of the straight line between origin and destination, following the WebMercator projection ```java double latToMercatorY(double lat) { double radLat = Math.toRadians(lat); return 6378137.0 * Math.log(Math.tan(Math.PI / 4 + radLat / 2)); } double lonToMercatorX(double lon) { return 6378137.0 * Math.toRadians(lon); } double lat1 = doc['OriginLocation'].value.getLat(); double lon1 = doc['OriginLocation'].value.getLon(); double lat2 = doc['DestLocation'].value.getLat(); double lon2 = doc['DestLocation'].value.getLon(); double x1 = lonToMercatorX(lon1); double y1 = latToMercatorY(lat1); double x2 = lonToMercatorX(lon2); double y2 = latToMercatorY(lat2); double midX = (x1 + x2) / 2; double midY = (y1 + y2) / 2; double midLat = mercatorYToLat(midY); double midLon = mercatorXToLon(midX); if (midLat < -90) midLat = -90; if (midLat > 90) midLat = 90; if (midLon < -180) midLon = -180; if (midLon > 180) midLon = 180; emit(midLat, midLon); ``` ### `midbearing` Is the angle between the North, the origin, and the destination ```java double latToMercatorY(double lat) { double radLat = Math.toRadians(lat); return 6378137.0 * Math.log(Math.tan(Math.PI / 4 + radLat / 2)); } double lonToMercatorX(double lon) { return 6378137.0 * Math.toRadians(lon); } double calculateBearing(double x1, double y1, double x2, double y2) { double angle = Math.toDegrees(Math.atan2(x2 - x1, y2 - y1)); return (angle + 360) % 360; // Ensure the angle is between 0 and 360 degrees } double lat1 = doc['OriginLocation'].value.getLat(); double lon1 = doc['OriginLocation'].value.getLon(); double lat2 = doc['DestLocation'].value.getLat(); double lon2 = doc['DestLocation'].value.getLon(); double x1 = lonToMercatorX(lon1); double y1 = latToMercatorY(lat1); double x2 = lonToMercatorX(lon2); double y2 = latToMercatorY(lat2); emit(calculateBearing(x1,y1,x2,y2)); ```