Tile Server

Introduction

If you are not using Mapbox or need complete customization, you can access the un-styled MapBox Vector tiles using the AirMap Tile Server API. However, we only recommend this for advanced users. If you just want to display AirMap airspace on a web page or mobile, check out our Maps Overview guide. The following example explains how to interact with the tile server in Javascript, but the same flow would apply to any other language.

Jurisdictions

Before we can get the actual airspace tiles, we need to know which airspace to retrieve in a given area since each area has different rules and airspace. To figure this out dynamically, there is a hidden layer jurisdictions that provides all of the different rulesets for a given tile. This includes national and local rules that are available. Let's start by adding this jurisdictions layer to the map:

๐Ÿšง

Private Jurisdictions and Rulesets (optional)

In order to access private jurisdictions and rulesets that only the user has access to, you will have to pass the logged in user's access token as a parameter in the tile url.

// On map load
map.on('load', () => {
  
  // Add Jurisdictions
  map.addLayer({
    "id": "jurisdictions",
    "type": "fill",
    "source": {
      type: 'vector',
      tiles: [ 'https://api.airmap.com/tiledata/v1/base-jurisdiction/{z}/{x}/{y}?access_token=$AIRMAP_ACCESS_TOKEN' ],
      "minzoom": 6,
      "maxzoom": 12
    },
    "source-layer": "jurisdictions",
    "minZoom": 6,
    "maxZoom": 22
  }, 'background')
  
}

Once we add this layer to the map, we need to parse the jurisdictions to get each ruleset for a tile. We can parse it (removing duplicate or empty jurisdictions) using a function like so:

function parseJurisdictions() {

  var layers = map.queryRenderedFeatures()

  //iterate through layers
  var jurisdictions = layers.filter(x => x.layer.source == "jurisdictions" && x.properties.jurisdiction)
  .map(feature => JSON.parse(feature.properties.jurisdiction))
  
  // remove duplicate or empty jurisdictions
  jurisdictions = jurisdictions.filter(x => x.rulesets.length > 0).filter((obj, pos, arr) => {
    return arr.map(mapObj => mapObj["uuid"]).indexOf(obj["uuid"]) === pos;
  });
  
  return jurisdictions;

}

Note that we should only parse the jurisdiction layer after it is loaded. We can check for that like this:

map.on('sourcedata', (data) => {

  // check for jurisdiction
  if(data.sourceId == 'jurisdictions' && data.isSourceLoaded) {
        // parse jurisdictions here...
  }

})

Rulesets

Now we will need to get the rulesets from our jurisdictions. Each jurisdiction will include features as rulesets, which we can obtain by using a function like so:

function addRulesets(jurisdictions) {
  
  let rulesets = [];
  jurisdictions.forEach(jurisdiction => {
    jurisdiction.rulesets.forEach(ruleset => {
      if (ruleset.selection_type == "pick1") {
        if (ruleset.id == "usa_part_107")
          rulesets.push(ruleset);
      } else {
        rulesets.push(ruleset);
      }
    })
  })
  return rulesets;
  
}

This will return all available rulesets present for the jurisdictions in a particular area. The rulesets should then be filtered based on their selection type: "required", "pick1", or "optional". In this example, we select US Part 107 rules as our pick1 option and we will be adding all optional rulesets as well.

Loading Layers

Now we will need to load the actual airspace layers from the jurisdictions. For each ruleset, we add a tile source (using the tile server API) and for each layer in that ruleset, we add a layer to our map.

rulesets.forEach(ruleset => {
  
  // Add vector tiles source for each ruleset
  map.addSource(ruleset.id, {
    type: 'vector',
    tiles: [ 'https://api.airmap.com/tiledata/v1/'+ruleset.id+'/'+ruleset.layers.join(',')+'/{z}/{x}/{y}?apikey=$AIRMAP_API_KEY&access_token=$AIRMAP_ACCESS_TOKEN' ],
    "minzoom": 6,
    "maxzoom": 12
  })

  ruleset.layers.forEach(layer => {
    // All layers for the ruleset
    let airspaceLayer = {
      "id": ruleset.id+"_"+layer,
      "source": ruleset.id,
      "source-layer": ruleset.id+"_"+layer,
      "type": "fill",
      "interactive": true,
      "paint": {
        "fill-opacity": 0.2,
        "fill-color": #1fa0d3
      }
    }

    map.addLayer(airspaceLayer, ruleset.id)
  })

})

Styling

Style within a single layer

For some layers, you may want to style tiles differently based on an attribute. In this case, you can modify the style properties of each layer before adding to the map. The tile server API will return different airspace information for each layer, so you can customize each layer based on its own properties.

Styles from AirMap CDN

You can also fetch map styles from AirMap's CDN for a specific theme and version. The returned JSON will have custom styles for layers which we can then add to our map. For example, we can use the following link to fetch map styles for the standard theme:

https://cdn.airmap.com/static/map-styles/0.8.5/standard.json

Example

The following code block shows an example in HTML/JavaScript/CSS of how to use the AirMap Tile Server API to retrieve tile data and add layers to a MapBox application.

<!DOCTYPE html>
<html lang="en">

<head>
    <title>AirMap | Maps SDK</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <script src="https://cdn.airmap.io/js/contextual-airspace/1.0.0/airmap.contextual-airspace-plugin.min.js" async=false defer=false></script> 
    <script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.39.1/mapbox-gl.js"></script>
    <link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.39.1/mapbox-gl.css" rel="stylesheet" />
    <style>
        body {
            margin:0;
            padding:0;
        }

        #map {
            position: absolute;
            width: 100%;
            height: 100%;
            z-index: -1
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script>
       
        // Include your airmap api key
        var AIRMAP_API_KEY = "{API KEY}"
        
        // Include your mapbox access token
        var MAPBOX_ACCESS_TOKEN = "{ACCESS TOKEN}"
        
        if (AIRMAP_API_KEY && MAPBOX_ACCESS_TOKEN) {
        mapboxgl.accessToken = MAPBOX_ACCESS_TOKEN
        

        // Create an instance of the map
        const map = new mapboxgl.Map({
            container: 'map',
            style: 'mapbox://styles/airmap/cipg7gw9u000jbam53kwpal1q',
            center: [-118.496475, 34.024212],
            zoom: 10
        })

        function getRandomColor() {
          var letters = '0123456789ABCDEF';
          var color = '#';
          for (var i = 0; i < 6; i++) {
            color += letters[Math.floor(Math.random() * 16)];
          }
          return color;
        }

        function addRulesets(jurisdictions){
  
          let rulesets = [];
          jurisdictions.forEach(jurisdiction => {
            jurisdiction.rulesets.forEach(ruleset => {
              if (ruleset.selection_type == "pick1") {
                if (ruleset.id == "usa_part_107")
                  rulesets.push(ruleset);
              } else {
                rulesets.push(ruleset);
              }
            })
          })
          return rulesets;
          
        }

        function parseJurisdictions(){
            var layers = map.queryRenderedFeatures()

            // Iterate through layers
            var jurisdictions = layers.filter(x => x.layer.source == "jurisdictions" && x.properties.jurisdiction)
                .map(feature => JSON.parse(feature.properties.jurisdiction))

            // Remove duplicate or empty jurisdictions
            jurisdictions = jurisdictions.filter(x => x.rulesets.length > 0).filter((obj, pos, arr) => {
                return arr.map(mapObj => mapObj["uuid"]).indexOf(obj["uuid"]) === pos;
            });

            return jurisdictions;
        }
        
      
        // Add layers
        map.on('load', () => {
            
            // Add Jurisdictions
            map.addLayer({
                "id": "jurisdictions",
                "type": "fill",
                "source": {
                    type: 'vector',
                    tiles: [ 'https://api.airmap.com/tiledata/v1/base-jurisdiction/{z}/{x}/{y}' ],
                    "minzoom": 6,
                    "maxzoom": 12
                },
                "source-layer": "jurisdictions",
                "minZoom": 6,
                "maxZoom": 22
            }, 'background')

        })

        map.on('sourcedata', (data) =>{

            // Check for jurisdiction
            if(data.sourceId == 'jurisdictions' && data.isSourceLoaded){

                // Parse jurisdiction
                var jurisdictions = parseJurisdictions();
                // Add rulesets
                var rulesets = addRulesets(jurisdictions);

                rulesets.forEach(ruleset => {
                    // Add Part 107 Source
                    map.addSource(ruleset.id, {
                        type: 'vector',
                        tiles: [ 'https://api.airmap.com/tiledata/v1/'+ruleset.id+'/'+ruleset.layers.join(',')+'/{z}/{x}/{y}?apiKey=$AIRMAP_API_KEY' ],
                        "minzoom": 6,
                        "maxzoom": 12
                    })
                    
                    ruleset.layers.forEach(layer => {
                        // All Controlled Airspace
                        let airspaceLayer = {
                            "id": ruleset.id+"_"+layer,
                            "source": ruleset.id,
                            "source-layer": ruleset.id+"_"+layer,
                            "type": "fill",
                            "interactive": true,
                            "paint": {
                                "fill-opacity": 0.4,
                                "fill-color": getRandomColor()
                            }
                        }

                        map.addLayer(airspaceLayer, ruleset.id)
                    })
            
                })
            }

        })

        } else {
            console.error(
                'Missing AIRMAP_API_KEY or MAPBOX_ACCESS_TOKEN. ' +
                'These are required for developing the Maps SDK locally.\n\n'
            );
        }



    </script>
</body>

</html>

Updated 2 years ago

Tile Server


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.