Using Polygons As Markers

By Dror Bogin

How I Got Into This

About a year ago my wife’s younger brother asked me to help him get started with programming (Thats lasted a week), I suggested he start with JavaScript since you get instant gratification when things work.

The kid likes airplanes so I thought a nice project will be making a simple airplanes map, something querying some open API and updating the map at intervals.

After asking around I got recommended to use OpenSky-Network's API

Which was a great idea, it's both accessible and gives you the location, barometric altitude, velocity, the bearing (in degrees from north), and whether the plane is on the ground or not. 

So after the kid gave up on actually trying, I wanted to keep making the project and see where it goes. My first idea was using leaflet.js but then I thought, what if the planes were not only in their updated location, but also show them at the correct altitude and facing the right way.

 

The Problems

1. Leaflet doesn't do 3D, so I had to switch to MapBox GL JS

2. MapBox GL JS needs polygons for 3D display, and the API only returns point data.

3. I also couldn't use an icon of a plane since icons don't have surface when displayed on a map. 

 

MapBox GL JS and Turf.js to the rescue.

 

The Solution

Since I couldn't use an icon, what I did was open QGIS with the highest resolution aerial photo/satellite imagery I could find of an airport.

I then digitized an airplane, and rotated the my new polygon layer to face north (more or less) and extracted its coordinates.


fetch('./plane.geojson')
  .then(response => response.json())
  .then(response => {
    plane = response;
    originalPlane = turf.transformRotate(plane, -31);
    coords = plane.features[0].geometry.coordinates[0]
  })

To use this polygon with the bearing degrees I get from the API I used Turf.js' transformRotate function to rotate the polygon to the right direction, I also added in transformScale since it's hard to see the actual size of an airplane in a continental web map (150X larger worked)

To do this, meaning convert the polygon to marker, what i did was create two arrays:


1. of distances between each coordinate and the centroid of the "template" polygon.

2. of bearing degrees between the centroid and each of the coordinates.

What you can do with these two arrays, is determine for each coordinate the direction and distance it should be from the centroid, which is the only coordinate we are getting back from the API.

 

![](/assets/img/blog/polygon_markers_1.png)



<p></p>

Using turf's destination function we can recreate the coordinates, based on the center of each new polygon, everywhere we want. After we know we can recreate the plane "icon" anywhere we want the thing left is to create a function that does exactly that, I also added the options to rotate and resize the polygon.

function polygonToMarker(center, coordinates, bearing, scale ){
    let distances = []
    let bearings = []
    let centroid = turf.center(turf.polygon([coordinates]))
    for(i in coordinates){
        to = turf.point(coordinates[i]);
        options = {units: 'kilometers'};
        distanceI = turf.distance(centroid, to, options);
        bearingI = turf.bearing(centroid, to);
        bearings.push(bearingI)
        distances.push(distanceI)
        }
    
    let points = []
    for(j in distances){
        distanceJ = distances[j]
        bearingJ = bearings[j]
        let point = turf.destination(center, distanceJ, bearingJ);
        points.push(point.geometry.coordinates)
    }
    let polygon = turf.polygon([points])
    // Rotate the polygon
    if(bearing){
        polygon = turf.transformRotate(polygon,bearing);
    }
    // Scale the polygon
    if(scale){
        polygon = turf.transformScale(polygon, scale);
    }
    return polygon
}

Using the base digitized plane polygon and all the point coordinates we get back from the API we can create a GeoJSON of polygon markers on our map anywhere we want, I chose to make them all 20 meters high, just so they'll seem to have some volume.


fetch('https://opensky-network.org/api/states/all')
    .then(response => response.json())
    .then(response => {
        let data = response;
        let geojson = {'type': "FeatureCollection", 'features':[]}
    
        for(p in data.states){
            if(data.states[p][5]){
                var feature  = {
                    type: "Feature",
                    properties:{
                        "icao24" : data.states[p][0],
                        "time_position":formattedTime,
                        "on_ground":data.states[p][8],
                        "velocity":data.states[p][9]*3.6,
                        "center":[data.states[p][5],data.states[p][6]],
                        "bearing":data.states[p][10],
                        "base_height":data.states[p][7],
                        "height":data.states[p][7]+20
                    },
                    geometry: {
                        type: "Polygon",
                        coordinates: polygonToMarker([data.states[p][5],data.states[p][6]],coords,data.states[p][10],150)
                    }
                }
                geojson.features.push(feature)
            }
        }
        map.getSource('planes').setData(geojson)
    })

The live map is available there as well, if you zoom in on {lng: 34.8987045, lat: 32.010007} you can see the original plane I used as a size comparison to all the others :

https://bogind.github.io/flights/

Share: Twitter Facebook LinkedIn