Log inSuggest an edit

Embed tiles in an app

This guide is for developers looking to embed Planet's imagery into their own mapping applications.

The Planet Data API provides images as large full-resolution geotiffs; this is great for many uses but quickly browsing images is not one of them.

That's why the Data API also comes with a tile service that allows external applications to browse imagery in real time. This service powers our Planet Explorer web interface and is also availiable to anyone with a Planet API key for use in their applications.

Tiling images is an efficient way to browse large amounts of raster and vector map data that would be much too large to render as a single map image. Tiles can be loaded on the fly as a user browses around a map to give the impression of a large seemless image.

Our tiles service interface is based on a loose standard called a Slippy Map that was popularized by the Open Street Map (OSM). If you're not familar with Slippy Maps see this introduction.

Sections:

Data API Tile Service

Our tiles service is an extension of our Data API and operates on the Data API concepts, Item Type and Item Id. Most items avaliable through the Data API can also be accessed in a tiled format using tiles.

The tiled imagery is a compressed version of the high quality visual assets availiable in the Data API.

The request format is:

https://tiles.planet.com/data/v1/<item_type>/<item_id>/<z>/<x>/<y>.png?api_key=<planet-api-key>

For example:

https://tiles.planet.com/data/v1/PSScene3Band/20161221_024131_0e19/14/12915/8124.png?api_key==your-api-key

In a typical street mapping tileset, any tile at any zoom level contains data. That's not the case with tiles. Each tiles request specifies a single Planet API item and only tiles that intersect with the the geometry of that item will have any image data in them.

To illustrate that, here's the same item as above at a higher zoom level, the tile only partially intersects the item.

https://tiles.planet.com/data/v1/PSScene3Band/20161221_024131_0e19/11/1614/1015.png?api_key==your-api-key

And here's a valid request for a tile that does not intersect the geometry of the item (it's a blank image)

https://tiles.planet.com/data/v1/PSScene3Band/20161221_024131_0e19/11/1/1.png?api_key=your-api-key

Embedding Tiles

Because tiles uses the Slippy Map format it can be easily embedded into applications that know how to work with tile servers. A popular open source tiling application is Open Layers.

Instead of providing Open Layers with the url to a single tile you can give it a template string and let it fill in the Z, X, Y values to pull tiles as a user browses around. Here's the template string for the item from earlier (note the literal {z}/{x}/{y} in the string):

"https://tiles.planet.com/data/v1/PSScene3Band/20161221_024131_0e19/{z}/{x}/{y}.png?api_key=<yourkey>"

And here's that tile template registered as a layer in a simple Open Layers javascript example. Open Street Map is also registered as a layer to make it easier to navigate.

<link rel="stylesheet" href="https://openlayers.org/en/v4.1.0/css/ol.css" type="text/css">
<script src="https://openlayers.org/en/v4.1.0/build/ol.js"></script>
<body>
  <div id="map" class="map"></div>
  <script>
    var planet_url = "https://tiles.planet.com/data/v1/PSScene3Band/20161221_024131_0e19/{z}/{x}/{y}.png?api_key=your-api-key"
    var map = new ol.Map({
      layers: [
        // Layer 1, Open Street Map base layer
        new ol.layer.Tile({
          source: new ol.source.OSM()
        }),
        // Layer 2, A single Planet item
        new ol.layer.Tile({
          source: new ol.source.XYZ({url: planet_url})
        })
      ],
      target: 'map',
      view: new ol.View({
        center: [0, 0],
        zoom: 0
      })
    });
  </script>
</body>

To run this, place it in a file named index.html and open it in a browser on your computer. Most of the world will be the Open Street Map Layer, but if you browse to an area that intersects with your Planet item (in this case near Singapore) you will see the two tile services composed together.

High Volume Tiles

In many applications that use tiles, the number of tile requests being made by the browser exceeds the maximum concurrent requests a browser will issue to a single url.

A common work around for this is to provide multiple identical tiling urls, the following are all valid tiles urls:

  • tiles1.planet.com
  • tiles2.planet.com
  • tiles3.planet.com

Libraries like Open Layers support this work around and allow you to specifiy the url pattern as one of the template string parameters:

https://tiles{1-3}.planet.com/data/v1/PSScene3Band/20161221_024131_0e19/{z}/{x}/{y}.png?api_key=your-api-key

Data API Example

The following example implements a Data API search request in javascript to gather all the cloudless scenes for a bounded geometry and add them as layers into an Open Layers Map.

<link rel="stylesheet" href="https://openlayers.org/en/v4.1.0/css/ol.css" type="text/css">
<script src="https://openlayers.org/en/v4.1.0/build/ol.js"></script>
<body>
  <div id="map" class="map"></div>
  <script>
    // a planet API key is required
    var planet_api_key = 'add me'

    // construct an Open Layers template string for a Planet item based
    // on its item_type and item_id
    function planet_template_url(item_type, item_id) {
      return 'https://tiles{1-3}.planet.com/data/v1/' + item_type +'/' + item_id + '/{z}/{x}/{y}.png?api_key=' + planet_api_key
    }

    // the geo json geometry object bounding Singapore
    geo_json_geometry = {
      "type": "Polygon",
      "coordinates": [
        [
          [
            103.45550537109375,
            1.0381511983133254
          ],
          [
            104.19845581054688,
            1.0381511983133254
          ],
          [
            104.19845581054688,
            1.5900668536691172
          ],
          [
            103.45550537109375,
            1.5900668536691172
          ],
          [
            103.45550537109375,
            1.0381511983133254
          ]
        ]
      ]
    }

    // a search filter for items the overlap with our chosen geometry
    geometry_filter = {
      "type": "GeometryFilter",
      "field_name": "geometry",
      "config": geo_json_geometry
    }

    // a search filter that filters out scenes with 50% or more cloud cover
    cloud_cover_filter = {
      "type": "RangeFilter",
      "field_name": "cloud_cover",
      "config": {
        "lte": 0.5
      }
    }

    // a search filter for a specific date range
    date_range_filter = {
      "type": "DateRangeFilter",
      "field_name": "acquired",
      "config": {
        "gte": "2017-01-01T00:00:00.000Z",
        "lte": "2017-01-08T00:00:00.000Z"
      }
    }

    // a search filter that combines our date range filter
    // with our geometry filter
    combined_filter = {
      "type": "AndFilter",
      "config": [geometry_filter, date_range_filter, cloud_cover_filter]
    }

    // make a search request against the Planet Data API with a specified filter
    // and item_type
    function get_items(item_type, filter, callback) {
      search_endpoint_request = {
        "item_types": [item_type],
        "filter": filter
      }
      var xhr = new XMLHttpRequest();
      xhr.open("POST", 'https://api.planet.com/data/v1/quick-search')
      xhr.setRequestHeader("Authorization", "Basic " + btoa(planet_api_key + ":"));
      xhr.setRequestHeader("Content-Type", "application/json");
      console.log(JSON.stringify(search_endpoint_request))
      xhr.onload = function (e) {
        if (xhr.readyState === 4) {
          console.log("made it")
          callback(JSON.parse(xhr.responseText))
        }
      }
      xhr.send(JSON.stringify(search_endpoint_request))
    }

    function build_open_layers_map(layers) {
      var map = new ol.Map({
        layers: layers,
        target: 'map',
        view: new ol.View({
          center: [0, 0],
          zoom: 0
        })
      })
    }


    // run the script
    get_items("PSScene3Band", combined_filter, function(response) {
      // add OSM as the first layer
      layers = [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        })
      ]
      // loop over planet items from the seach api
      // and add them as layers
      response["features"].forEach(feature =>{
        layers.push(
          new ol.layer.Tile({
            source: new ol.source.XYZ({url: planet_template_url("PSScene3Band", feature["id"])})
          })
        )
      })

      build_open_layers_map(layers)
    })
  </script>
</body>