The Engine is stateless between queries, so each request must contain all context that is relevant to the request.

Every API request must contain an Authorization HTTP header containing either a valid API key or a valid JWT token.

The Engine supports a number of input and output formats, which are determined by the query in question.

Getting started

In order to use our API, you will need:

Depending on how much you are going to use the system, you may need to set up a subscription.

We currently have a Python package available. We provide instructions for using that Python package, but for other languages (Javascript, Julia, Go and R) we provide instructions on how to use the REST API directly. For those languages we assume that you are at least somewhat familiar with the following:

You can of course use any programming language to query the REST API.

Obtaining API keys

You need a valid API key or a valid JWT token to conduct any API request. Each API key corresponds to a specific EarthOS user. We recommend using API keys for almost all use cases.

Once you have an API, you can get an API key from your organization settings page.

Using the API

Our API is a RESTful HTTP API with collection of endpoints designed primarily for programmatic access, for example from within an application. Common use cases include:

  • Small experimental programs in utility languages such as Python, R or Julia.
  • Larger applications which rely on our API to visualize our data or augment your own.
  • Batch programs which fetch data periodically as part of a larger processing set.

If you just want to start testing the API, you can use a programming language of your choice or some tool that makes making REST API queries easy. For example:

In our API Reference, you will find a detailed explanation of each individual endpoint we offer along with examples in various programming languages.

Note that most of our API endpoints expect that you provide a formula written in our Formula Language.

A screenshot of the Postman application querying our API. We don’t specifically support or endorse Postman, but it is pretty good.

Getting set up

Whichever tool you use, you’re going to have to do a bit of setup. The steps are:

  1. Authorization
  2. Choosing a query endpoint
  3. Setting up the query parameters
  4. Handling the output

In the following sub-sections, we’ll go through each of these in turn.

Authorization

  GET YOUR_QUERY_PATH HTTP/1.1
Host: engine.stratisintel.com
Authorization: Bearer YOUR_API_TOKEN
  

Your API key is a Bearer Token. It should be passed as a HTTP Authorization header, prefixed with “Bearer”.

Many tools will support some simple way to indicate that you have a Bearer token.

Choosing a query endpoint

You should refer to the Engine Reference to select an endpoint that gives you the type of data you need.

Be careful to note what HTTP verb the endpoint takes. It matters whether endpoints take GET or POST requests, for example.

The current query endpoints supported are:

Name URL
Engine info /
Engine status /status/
Engine Prometheus metrics /metrics
Engine version /version/
Query analyzer /analyze/
Query extrema /query/minmax/
Query dopesheet /query/dopesheet
Point /point/
Points /points/
Table /ts/table/
Map Region /map/
Map Tile /map/{x}/{y}/{z}/
List Functions /functions/
List Variables /variables/
Variable info /variables/{name}/
Variable dopesheet /variables/{name}/dopesheet/
Colorscale (Deprecated) /cs/

Setting up the query parameters

In the Engine Reference, all the parameters available for any given query type are listed. Some may be optional.

Parameter names are case-sensitive, and no spaces are allowed in parameter names. If you get an error saying that you’re missing a parameter you think you included, you probably either misspelled it or included some extra characters. It happens!

Handling the output

The output you’ll get from our Engine varies depending on the query you’re making. Frequently it’s one of these:

  • JSON (our most common response format; also used when reporting errors)
  • PNG
  • CSV
  • pfPNG

More information about supported formats is available in our chapter on Internals.

Formula Language

Almost all Engine queries take a formula parameter, which is expected to be a valid statement or program in Stratis’s formula language, which is evaluated as part of the production of the result.

Stratis’s formula language is a domain-specific language for describing spatiotemporal relationships between arbitrary data relative to the surface of a planet. A program written in the language is always evaluated at a given set of spacetime coordinates, and upon evaluation will provide a value for that location in spacetime.

This language is not intended to be a general purpose programming language. It does however function as an efficient way to obtain significant amounts of complex data.

Variables

Variables in the Engine come are statically typed, but lazily evaluated, and the evaluation is dependent on the current spacetime. As such, it is possible to think of data variables as functions of spacetime, but we refer to them as variables nonetheless because of the underlying data-driven nature.

Variables in the language often are prefixed with a namespace followed by a name, separated by a period. So, for instance, the GFS weather forecast’s air temperature variable can be referenced as gfs.air_temperature

In order to get a list of currently available variables in the system, make a call to the /variables/ endpoint.

Four special variables exist:

  • latitude
  • longitude
  • altitude
  • time

These will evaluate to the spacetime location where the variable is being evaluated.

Conditionals

We support conditional expressions of the form:

condition ? true_expr : false_expr

For example, you might say:

latitude > 60 ? gfs.air_pressure : gfs.air_pressure - 10

Operators and functions

Much of the power of our Engine comes from being able to perform mathematical operations on data variables without having to worry about where the data comes from or how it’s managed – we’ll take care of those details for you, or let you know if you’re trying to do something we don’t know how to handle.

Operators

Operator Name Description
+ Addition Arithmetic addition
- Subtraction Arithmetic subtraction
* Multiplication Arithmetic multiplication
/ Division Arithmetic division
^ Power Left value raised to the exponent of right value
< Less than Returns 0 if false, 1 if true
<= Less than or equal Returns 0 if false, 1 if true
== Equal Returns 0 if false, 1 if true
!= Not equal Returns 0 if false, 1 if true
>= Greater than or equal Returns 0 if false, 1 if true
> Greater than Returns 0 if false, 1 if true
` `

Common mathematical functions

Function Description
sqrt(x) Square root.
abs(x) Absolute value.
log10(x) Logarithm base 10.
log2(x) Logarithm base 2.
loge(x) Logarithm base e.
round(x) Round to nearest integer.
floor(x) Round down.
ceil(x) Round up.
sign(x) Sign of value; -1 if negative, 1 if positive; 0 if 0.
clamp(x, a, b) Clamp x between a and b.
heaviside(a, b) Heaviside step function.

Trigonometric functions

All trigonometric functions operate in radians.

Function Description
sin(x) Sine.
cos(x) Cosine.
tan(x) Tangent.
asin(x) Arcsine.
acos(x) Arccosine.
atan(x) Arctangent.
sec(x) Secant.
csc(x) Cosecant.
ctan(x) Cotangent.
asec(x) Inverse secant.
acsc(x) Inverse cosecant.
acot(x) Inverse cotangent.
sinh(x) Hyperbolic sine.
cosh(x) Hyperbolic cosine.
tanh(x) Hyperbolic tangent.
sech(x) Hyperbolic secant.
csch(x) Hyperbolic cosecant.
coth(x) Hyperbolic cotangent.
asinh(x) Inverse hyperbolic sine.
acosh(x) Inverse hyperbolic cosine.
atanh(x) Inverse hyperbolic tangent.
asech(x) Inverse hyperbolic secant.
acsch(x) Inverse hyperbolic cosecant.
acoth(x) Inverse hyperbolic cotangent.

Random functions

Function Description
rand() Random number between 0 and 1. Uniform distribution.

More random functions will be added soon.

Spacetime

Most of the work of the Engine boils down to evaluating equations at different points in spacetime. Because we mostly care about evaluating equations relative to planets, we treat spacetime as a 4-dimensional geometry, consists of:

  • Latitude, relative to the equator and poles
  • Longitude, relative to a given prime meridian (Greenwich, for Earth)
  • Altitude, relative to a given datum
  • Time, relative to a given epoch

With these four spacetime coordinates, we can locate anything on Earth or other celestial bodies with high precision.

We always work with these with the following names and limits:

Element Description
latitude Given in decimal degrees, between -90 (south pole) and 90 (north pole).
longitude Given in decimal degrees, between -180 (west) and 180 (east).
altitude Given in meters relative to the planet’s datum. For Earth we calibrate this to the WGS84 standard geoid, although due to data quality issues, some variables may be relative to other datums. Negative numbers are below the datum, positive numbers above it.
time Given in seconds since 00:00:00 1. January 1970, commonly known as a UNIX timestamp. System time granularity is 1 second and 64 signed bits, allowing dates from ±292 billion years relative to 1970. This may be expanded in the future to allow additional fractional time of 64 bits, allowing femtosecond accuracy.

Spacetime offsets

Example: We want to calculate how much windier it is today than yesterday:

  era5.wind_speed - era5.wind_speed[time: -1 day]
  

Example: We want to see how much higher the forecast temperature is today than the average over the past three years:

  gfs.air_temperature - 
(
  era5.air_temperature[time: -1 year] +
  era5.air_temperature[time: -2 years] + 
  era5.air_temperature[time: -3 years] + 
) / 3.0
  

Example: If you wanted to know the air pressure difference between Tahiti (lat: -17.6509, lon: -149.4260) and Darwin (lat: -12.4637, lon: 130.8444), you could specify it as:

  gfs.air_pressure @ [latitude: -12.4637, longitude:  130.8444] - 
gfs.air_pressure @ [latitude: -17.6509, longitude: -149.4260]
  

This is actually the start of the calculation of the Southern Oscillation Index.

Evaluating a bunch of points is one thing, but sometimes you want to evaluate parts of an expression at different points in spacetime. For instance, if you want to see how much warmer it will be tomorrow than today, you might want to say: “Temperature tomorrow minus temperature today”. If you’re evaluating this at “today”, then “tomorrow” is a relative offset in the time dimension.

While most endpoints will have you specify the extents you want to work with (such as the map region), it is possible to specify offsets on individual variables or expressions in order to have them be evaluated at different locations in spacetime.

There are two ways to specify such offsets: relative and absolute.

For each offset, you can offset any of the spacetime coordinates (latitude, longitude, altitude and time).

Relative offsets will shift the variable’s evaluation point relative to the effective spacetime. To specify a relative offset, use square brackets after a variable, and specify within it which coordinates to shift (e.g. myvariable[time: -3 days, altitude: +80m]).

Absolute offsets will move the variable’s evaluation point to a specified location, replacing the specified parts of the effective spacetime. To specify an absolute offset, use an “@” symbol, followed by square brackets after a variable, and specify within it which coordinates to update. myvariable @ [altitude: 2m]

Spacetime stack

Example: You might want to take our previous example of the air pressure difference between Darwin and Tahiti and ask how difference that is now compared to this time last year?

  (
  gfs.air_pressure @ [latitude: -12.4637, longitude:  130.8444] - 
  gfs.air_pressure @ [latitude: -17.6509, longitude: -149.4260]
) 
  -
(
  gfs.air_pressure @ [latitude: -12.4637, longitude:  130.8444] - 
  gfs.air_pressure @ [latitude: -17.6509, longitude: -149.4260]
)[time: -1 year]
  

But wouldn’t it be useful if you could combine offsets, for example a relative offset in time and an absolute offset in space? In the above examples, we mention effective spacetime but didn’t define it! The effective spacetime is simply the total of all the shifts that have occurred so far.

When we’re figuring out where a particular variable is evaluated, imagine that the “closer” the offset is to the variable, the higher priority it gets. And there are two rules:

  • Absolute offsets override the previous value.
  • Relative offsets add up.

Here’s a crazy expression:

  (  
    (
         (
            ( era5.soil_water_level @[altitude:10])[latitude: 2.3, longitude: 2.3] 
         )[time: -1 day]
    )@[longitude: -19]
)@[time: 129422832, latitude: 50.3, longitude: 22.9, altitude: 2]
  

Hopefully nobody would ever write this kind of expression in practice. It’s a contrived idea to help illustrate how the spacetime stack works.

Let’s take a crazy example (see the “crazy expression” in the code examples on the side).

In the table below, we show both the changes on each sub-expression, and the “set” fields show what the aggregate value is once the stack has been read through. The final outcome is at the top. As you can see, the last thing we do is add the absolute altitude change (@[altitude:10]):

type lat lon alt time set lat set lon set alt set time
absolute 10 52.6 -16.7 10 129336432
relative +2.3 +2.3 52.6 -16.7 2 129336432
relative -1 day 50.3 -19 2 129336432
absolute -19 50.3 -19 2 129422832
absolute 50.3 22.9 2 129422832 50.3 22.9 2 129422832

List Variables

  from earthos import EarthOS
eo = EarthOS('YOUR_API_KEY')
vars = eo.get_variables()
  
  library(httr)

api_key <- "YOUR_API_KEY"
url <- "https://engine.stratisintel.com/variables/"

headers <- c("Authorization" = paste("Bearer", api_key))

response <- GET(url, add_headers(headers))

print(content(response, "parsed"))
  
  package main

import (
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {
    apiKey := "YOUR_API_KEY"
    url := "https://engine.stratisintel.com/variables/"

    req, _ := http.NewRequest("GET", url, nil)
    req.Header.Set("Authorization", "Bearer " + apiKey)

    client := &http.Client{}
    resp, _ := client.Do(req)

    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)

    fmt.Println(string(body))
}
  
  using HTTP

api_key = "YOUR_API_KEY"
url = "https://engine.stratisintel.com/variables/"

headers = Dict("Authorization" => "Bearer $api_key")

response = HTTP.get(url, headers=headers)

println(String(response.body))
  
  const fetch = require('node-fetch');

const api_key = 'YOUR_API_KEY';
const url = 'https://engine.stratisintel.com/variables/';

const headers = {
  'Authorization': `Bearer ${api_key}`
};

fetch(url, { headers })
  .then(response => response.json())
  .then(data => console.log(data));
  

This endpoint allows you to get a list of variables available to the Engine.

HTTP Request

GET https://engine.stratisintel.com/variables/

Evaluate a Point

  from earthos import EarthOS
eo = EarthOS('YOUR_API_KEY')

formula = '(gfs.air_temperature * 9 / 5) + 32'
latitude = 37.7749
longitude = -122.4194
altitude = 2
time = 1675800900

value = eo.get_point(latitude, longitude, time, formula)
  
  package main

import (
  "io"
  "fmt"
  "net/http"
  "net/url"
)

func main() {
  apiKey := "YOUR_API_KEY"
  apiUrl := "https://engine.stratisintel.com/point/"

  client := &http.Client{}
  req, err := http.NewRequest("GET", apiUrl, nil)
  if err != nil {
    fmt.Println("Error creating request:", err)
    return
  }

  req.Header.Add("Authorization", "Bearer "+apiKey)

  params := url.Values{}
  params.Set("formula", "(gfs.air_temperature * 9 / 5) + 32")
  params.Set("latitude", "37.7749")
  params.Set("longitude", "-122.4194")
  params.Set("altitude", "2")
  params.Set("time", "1675800900")

  req.URL.RawQuery = params.Encode()

  resp, err := client.Do(req)
  if err != nil {
    fmt.Println("Error sending request:", err)
    return
  }
  defer resp.Body.Close()

  b, err := io.ReadAll(resp.Body)

  fmt.Println(string(b))
}
  
  library(httr)

api_key <- "YOUR_API_KEY"
url <- "https://engine.stratisintel.com/point/"

headers <- c(
  Authorization = paste("Bearer", api_key)
)

params <- list(
  formula = "(gfs.air_temperature * 9 / 5) + 32",
  latitude = 37.7749,
  longitude = -122.4194,
  altitude = 2,
  time = 1675800900
)

response <- GET(url, add_headers(headers), query = params)

content(response, "parsed")
  
  using HTTP

api_key = "YOUR_API_KEY"
url = "https://engine.stratisintel.com/point/"

headers = [
    "Authorization" => "Bearer $api_key"
]

params = Dict(
    "formula" => "(gfs.air_temperature * 9 / 5) + 32",
    "latitude" => 37.7749,
    "longitude" => -122.4194,
    "altitude" => 2,
    "time" => 1675800900
)

response = HTTP.get(url, headers=headers, query=params)
  
  const axios = require('axios');

const apiKey = 'YOUR_API_KEY';
const url = 'https://engine.stratisintel.com/point/';

const headers = {
  Authorization: `Bearer ${apiKey}`
};

const params = {
  formula: '(gfs.air_temperature * 9 / 5) + 32',
  latitude: 37.7749,
  longitude: -122.4194,
  altitude: 2,
  time: 1675800900
};

axios.get(url, { headers, params })
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.log('Error:', error);
  });
  

Example output (JSON):

  {
  "engine": "Stratis Formula Engine v0.3.1", 
  "formula": "(gfs.air_temperature * 9 / 5) + 32", 
  "spacetime": {
    "latitude": 37.774899, 
    "longitude": -122.419403, 
    "altitude": 2, 
    "time": 1675800900
  }, 
  "error": {"type": "NoError", "msg": ""}, 
  "result": 49.785204
}
  

This endpoint evaluates a single point at a given spacetime. Note that it is faster and more efficient (not to mention cheaper) to use the Points endpoint if you have more than one point you’d like to evaluate.

HTTP Request

GET https://engine.stratisintel.com/point/

Parameters

Parameter Description
formula A formula expressed in the data language.
latitude The latitude to evaluate at, in decimal degrees.
longitude The longitude to evaluate at, in decimal degrees.
altitude The altitude to evaluate at, in meters.
time The time to evaluate at, in seconds.

Evaluate multiple Points

  from earthos import EarthOS
eo = EarthOS('YOUR_API_KEY')
payload = {
    "defaults": {
        "formula": "gfs.air_temperature",
        "timestamp": 1679933282,
        "altitude": 2,
        "latitude": -20
    },
    "points": [
        {
            "id": "1",
            "latitude": 66.0,
            "longitude": 23.0
        },
        {
            "id": "2",
            "latitude": 67.0,
            "longitude": 22.0
        },
        {
            "id": "3",
            "latitude": 60.0,
            "longitude": 20.0
        }
    ]
}

response = eo.get_points(payload)
print(response)
  
  library(httr)

api_key <- "YOUR_API_KEY"
url <- "https://engine.stratisintel.com/points/"

payload <- list(
  defaults = list(
    formula = "gfs.air_temperature",
    timestamp = 1679933282,
    altitude = 2,
    latitude = -20
  ),
  points = list(
    list(
      id = "1",
      latitude = 66.0,
      longitude = 23.0
    ),
    list(
      id = "2",
      latitude = 67.0,
      longitude = 22.0
    ),
    list(
      id = "3",
      latitude = 60.0,
      longitude = 20.0
    )
  )
)

headers <- c(
  'Content-Type' = 'application/json',
  'Authorization' = paste("Bearer", api_key)
)

response <- POST(url, body = toJSON(payload), encode = "json", add_headers(headers))

content <- content(response, as = "text")
data <- fromJSON(content)

print(data)
  
  package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

func main() {
    apiKey := "YOUR_API_KEY"
    url := "https://engine.stratisintel.com/points/"

    payload := map[string]interface{}{
        "defaults": map[string]interface{}{
            "formula":   "gfs.air_temperature",
            "timestamp": 1679933282,
            "altitude":  2,
            "latitude":  -20,
        },
        "points": []map[string]interface{}{
            {
                "id":        "1",
                "latitude":  66.0,
                "longitude": 23.0,
            },
            {
                "id":        "2",
                "latitude":  67.0,
                "longitude": 22.0,
            },
            {
                "id":        "3",
                "latitude":  60.0,
                "longitude": 20.0,
            },
        },
    }

    jsonPayload, err := json.Marshal(payload)
    if err != nil {
        fmt.Println("Error marshaling payload:", err)
        return
    }

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload))
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }

    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer "+apiKey)

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer resp.Body.Close()

    var data map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&data)
    if err != nil {
        fmt.Println("Error decoding response:", err)
        return
    }

    fmt.Println(data)
}
  
  using HTTP
using JSON

api_key = "YOUR_API_KEY"
url = "https://engine.stratisintel.com/points/"

payload = Dict(
    "defaults" => Dict(
        "formula" => "gfs.air_temperature",
        "timestamp" => 1679933282,
        "altitude" => 2,
        "latitude" => -20
    ),
    "points" => [
        Dict("id" => "1", "latitude" => 66.0, "longitude" => 23.0),
        Dict("id" => "2", "latitude" => 67.0, "longitude" => 22.0),
        Dict("id" => "3", "latitude" => 60.0, "longitude" => 20.0)
    ]
)

headers = Dict(
    "Content-Type" => "application/json",
    "Authorization" => "Bearer $api_key"
)

response = HTTP.post(url, headers, JSON.json(payload))

data = JSON.parse(String(response.body))

println(data)
  
  const axios = require('axios');

const apiKey = 'YOUR_API_KEY';
const url = 'https://engine.stratisintel.com/points/';

const payload = {
  defaults: {
    formula: 'gfs.air_temperature',
    timestamp: 1679933282,
    altitude: 2,
    latitude: -20
  },
  points: [
    {
      id: '1',
      latitude: 66.0,
      longitude: 23.0
    },
    {
      id: '2',
      latitude: 67.0,
      longitude: 22.0
    },
    {
      id: '3',
      latitude: 60.0,
      longitude: 20.0
    }
  ]
};

const headers = {
  'Content-Type': 'application/json',
  'Authorization': `Bearer ${apiKey}`
};

axios.post(url, payload, { headers })
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.log('Error:', error);
  });
  

Example output (JSON)

  {
  "engine": "Stratis Formula Engine v0.3.1", 
  "queries": 3, 
  "errors": 0, 
  "points": [
    {
      "id": "1", 
      "value": -5.775804, 
      "formula": "gfs.air_temperature", 
      "spacetime": {
        "latitude": 66, 
        "longitude": 23, 
        "altitude": 2, 
        "time": 1679933282
      }, 
      "error": {"type": "NoError", "msg": ""}
    }, 
    {
      "id": "2", 
      "value": -7.202588, 
      "formula": "gfs.air_temperature", 
      "spacetime": {
        "latitude": 67, 
        "longitude": 22, 
        "altitude": 2, 
        "time": 1679933282
      }, 
      "error": {"type": "NoError", "msg": ""}
    }, 
    {
      "id": "3", 
      "value": -3.715742, 
      "formula": "gfs.air_temperature", 
      "spacetime": {
        "latitude": 60, 
        "longitude": 20, 
        "altitude": 2, 
        "time": 1679933282
      }, 
      "error": {"type": "NoError", "msg": ""}
    }
  ]
}
  

This endpoint takes a JSON payload on the POST request. Given a set of point-queries, gives a set of results. Each point query MUST have an ID. The IDs SHOULD be unique, but the solver doesn’t actually validate this.

Rather than requiring a fully qualified spacetime and formula for every point, the query MAY have a defaults object, containing fallback values. If a specific point query doesn’t have a value stated, it will fall back to the defaults object.

However, if the value is missing from the formula and no default is provided, the response will yield a SpacetimeError.

Using Points rather than Point

There are many cases where you might want to query a set of sparse points, that are:

  • In the same place, but at different times
  • In different places, at the same time
  • At different places and times, for example, along a path
  • At the same time and place, but at different altitudes
  • At the same time, place and altitude, but you want more than one formula to be evaluated
  • Any mixture of the above

In this case, it’s probably better to use one Points query, rather than many consecutive Point queries. Main reasons:

  • Even though it might be slower than one Point query, it’ll typically be faster than doing all of the point queries separately.
  • It’ll cost you less, because of how usage is calculated.

HTTP Request

POST https://engine.stratisintel.com/points/

Evaluate a Map Region

  from earthos import EarthOS, EOColorScale
north = 30
south = -30
east = 60
west = -60
time = 1687266935
size_x = 800
size_y = 400
eo = EarthOS('YOUR_API_KEY')
region = eo.get_region(north, south, east, west, time, 'gfs.lwe_precipitation_sum', size_x, size_y)

cs = EOColorScale(...) # Apply a color scale
region.save("region.png", cs)
  
  library(httr)

api_key <- "YOUR_API_KEY"
url <- "https://engine.stratisintel.com/map/"

headers <- c('Authorization' = paste("Bearer", api_key))

params <- list(
  time = 1687266935,
  formula = "gfs.lwe_precipitation_sum",
  min = 0,
  max = 90,
  colors = "6f6f6fff,3c74a0ff,3ba1a1ff,3ba13dff,82a13bff,a1a13bff,a13b3bff,a13ba1ff,a8a8a8ff",
  offsets = "0,0.012,0.12,0.16,0.2,0.3,0.4,0.62,1",
  north = 30,
  south = -30,
  east = 60,
  west = -60,
  width = 600,
  height = 300
)

response <- GET(url, add_headers(headers), query = params)

writeBin(content(response, "raw"), "map.png")
  
  package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
)

func main() {
    apiKey := "YOUR_API_KEY"
    url := "https://engine.stratisintel.com/map/"

    client := &http.Client{}
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }

    req.Header.Set("Authorization", "Bearer "+apiKey)

    q := req.URL.Query()
    q.Add("time", "1687266935")
    q.Add("formula", "gfs.lwe_precipitation_sum")
    q.Add("min", "0")
    q.Add("max", "90")
    q.Add("colors", "6f6f6fff,3c74a0ff,3ba1a1ff,3ba13dff,82a13bff,a1a13bff,a13b3bff,a13ba1ff,a8a8a8ff")
    q.Add("offsets", "0,0.012,0.12,0.16,0.2,0.3,0.4,0.62,1")
    q.Add("north", "30")
    q.Add("south", "-30")
    q.Add("east", "60")
    q.Add("west", "-60")
    q.Add("width", "800")
    q.Add("height", "400")
    req.URL.RawQuery = q.Encode()

    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response body:", err)
        return
    }

    err = ioutil.WriteFile("map.png", body, 0644)
    if err != nil {
        fmt.Println("Error writing file:", err)
        return
    }

    fmt.Println("Map saved as map.png")
}
  
  using HTTP
using FileIO

api_key = "YOUR_API_KEY"
url = "https://engine.stratisintel.com/map/"

headers = Dict("Authorization" => "Bearer $api_key")

params = Dict(
    "time" => 1687266935,
    "formula" => "gfs.lwe_precipitation_sum",
    "min" => 0,
    "max" => 90,
    "colors" => "6f6f6fff,3c74a0ff,3ba1a1ff,3ba13dff,82a13bff,a1a13bff,a13b3bff,a13ba1ff,a8a8a8ff",
    "offsets" => "0,0.012,0.12,0.16,0.2,0.3,0.4,0.62,1",
    "north" => 30,
    "south" => -30,
    "east" => 60,
    "west" => -60,
    "width" => 800,
    "height" => 400
)

response = HTTP.get(url, headers=headers, query=params)

filename = "map.png"
save(filename, response.body)
  
  const axios = require('axios');
const fs = require('fs');

const apiKey = 'YOUR_API_KEY';
const url = 'https://engine.stratisintel.com/map/';

const headers = {
  Authorization: `Bearer ${apiKey}`
};

const params = {
  time: 1687266935,
  formula: 'gfs.lwe_precipitation_sum',
  min: 0,
  max: 90,
  colors: '6f6f6fff,3c74a0ff,3ba1a1ff,3ba13dff,82a13bff,a1a13bff,a13b3bff,a13ba1ff,a8a8a8ff',
  offsets: '0,0.012,0.12,0.16,0.2,0.3,0.4,0.62,1',
  north: 30,
  south: -30,
  east: 60,
  west: -60,
  width: 800,
  height: 400
};

axios.get(url, { headers, params, responseType: 'arraybuffer' })
  .then(response => {
    fs.writeFileSync('map.png', response.data);
    console.log('Map saved as map.png');
  })
  .catch(error => {
    console.log('Error:', error);
  });
  

Example output (PNG)

This endpoint takes a given geographic region and evaluates the given formula at every point given in it.

Depending on which format you select, certain parameters may be optional. For example, parameters relating to the color scale are only required if you ask for png, but is meaningless when requesting pfpng or csv.

HTTP Request

GET https://engine.stratisintel.com/map/

Parameters

Parameter Description
formula A formula expressed in the data language.
format The desired output format. Default: png. Allowed: png, pfpng and csv.
north The northern limit of the requested region, in decimal degrees.
south The southern limit of the requested region, in decimal degrees.
east The eastern limit of the requested region, in decimal degrees.
west The western limit of the requested region, in decimal degrees.
altitude The altitude to evaluate at, in meters.
time The time to evaluate at, in seconds.
colors Colors for the color scale. Required for format=png.
offsets Offsets for the color scale. Required for format=png.
max Maximum value for color scale. Defaults to 100.
min Minimum value for color scale. Defaults to 0.
interpolation Which interpolation method to use. Possible values: none, bilinear.
width Width of the resulting map in pixels (or columns)
height Height of the resulting map in pixels (or rows)

Evaluate a Map Tile

  from earthos import EarthOS, EOColorScale

# Map tile coordinates to look up
x = 3
y = 2
z = 3

eo = EarthOS('YOUR_API_KEY')
tile = eo.get_tile(x, y, z, 1682290466, 'gfs.air_pressure')

# Apply a color scale
cs = EOColorScale(
    colors = [
        (0x08, 0x10, 0x30, 0xff),(0x00, 0x20, 0x60, 0xff),(0x00, 0x34, 0x92, 0xff),(0x00, 0x5a, 0x94, 0xff),(0x00, 0x75, 0x92, 0xff),(0x1a, 0x8c, 0x93, 0xff),(0x67, 0xa2, 0x9b, 0xff),(0x9b, 0xb7, 0xac, 0xff),(0xb6, 0xb6, 0xb6, 0xff),(0xb0, 0xae, 0x98, 0xff),(0xa7, 0x93, 0x6b, 0xff),(0xa3, 0x74, 0x43, 0xff),(0x9f, 0x51, 0x2c, 0xff),(0x8e, 0x2f, 0x39, 0xff),(0x6f, 0x18, 0x40, 0xff),(0x30, 0x08, 0x18, 0xff)],
    offsets=[0.0,0.27,0.42,0.47,0.52,0.56,0.59,0.61,0.62,0.64,0.66,0.68,0.72,0.76,0.81,1.0],
    min=750,
    max=1100
)

tile.save("tile.png", cs)
  
  library(httr)

x <- 3
y <- 2
z <- 3

api_key <- "YOUR_API_KEY"
url <- sprintf("https://engine.stratisintel.com/maps/%d/%d/%d/", x, y, z)

headers <- c('Authorization' = paste("Bearer", api_key))

params <- list(
  'time' = 1682290466,
  'formula' = 'gfs.air_pressure',
  'min' = 750,
  'max' = 1100,
  'colors' = '081030ff,002060ff,003492ff,005a94ff,007592ff,1a8c93ff,67a29bff,9bb7acff,b6b6b6ff,b0ae98ff,a7936bff,a37443ff,9f512cff,8e2f39ff,6f1840ff,300818ff',
  'offsets' = '0.0,0.2777777777777778,0.4222222222222222,0.4777777777777778,0.5277777777777778,0.5666666666666667,0.5944444444444444,0.6180555555555556,0.6291666666666667,0.6402777777777777,0.6611111111111111,0.6888888888888889,0.7222222222222222,0.7666666666666667,0.8111111111111111,1.0'
)

response <- GET(url, add_headers(headers), query = params)

writeBin(content(response, "raw"), "tile.png")
  
  package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
)

func main() {
    x := 3
    y := 2
    z := 3

    apiKey := "YOUR_API_KEY"
    url := fmt.Sprintf("https://engine.stratisintel.com/maps/%d/%d/%d/", x, y, z)

    headers := map[string]string{
        "Authorization": "Bearer " + apiKey,
    }

    params := map[string]string{
        "time":    "1682290466",
        "formula": "gfs.air_pressure",
        "min":     "750",
        "max":     "1100",
        "colors":  "081030ff,002060ff,003492ff,005a94ff,007592ff,1a8c93ff,67a29bff,9bb7acff,b6b6b6ff,b0ae98ff,a7936bff,a37443ff,9f512cff,8e2f39ff,6f1840ff,300818ff",
        "offsets": "0.0,0.2777777777777778,0.4222222222222222,0.4777777777777778,0.5277777777777778,0.5666666666666667,0.5944444444444444,0.6180555555555556,0.6291666666666667,0.6402777777777777,0.6611111111111111,0.6888888888888889,0.7222222222222222,0.7666666666666667,0.8111111111111111,1.0",
    }

    client := &http.Client{}
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }

    req.Header.Set("Authorization", headers["Authorization"])

    q := req.URL.Query()
    for key, value := range params {
        q.Add(key, value)
    }
    req.URL.RawQuery = q.Encode()

    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response body:", err)
        return
    }

    err = ioutil.WriteFile("tile.png", body, 0644)
    if err != nil {
        fmt.Println("Error writing file:", err)
        return
    }

    fmt.Println("Tile saved as tile.png")
}
  
  using HTTP
using FileIO

x = 3
y = 2
z = 3

api_key = "YOUR_API_KEY"
url = "https://engine.stratisintel.com/maps/$x/$y/$z/"

headers = Dict("Authorization" => "Bearer $api_key")

params = Dict(
    "time" => 1682290466,
    "formula" => "gfs.air_pressure",
    "min" => 750,
    "max" => 1100,
    "colors" => "081030ff,002060ff,003492ff,005a94ff,007592ff,1a8c93ff,67a29bff,9bb7acff,b6b6b6ff,b0ae98ff,a7936bff,a37443ff,9f512cff,8e2f39ff,6f1840ff,300818ff",
    "offsets" => "0.0,0.2777777777777778,0.4222222222222222,0.4777777777777778,0.5277777777777778,0.5666666666666667,0.5944444444444444,0.6180555555555556,0.6291666666666667,0.6402777777777777,0.6611111111111111,0.6888888888888889,0.7222222222222222,0.7666666666666667,0.8111111111111111,1.0"
)

response = HTTP.get(url, headers=headers, params=params)

write("tile.png", response.body)
  
  const axios = require('axios');
const fs = require('fs');

const x = 3;
const y = 2;
const z = 3;

const apiKey = 'YOUR_API_KEY';
const url = `https://engine.stratisintel.com/maps/${x}/${y}/${z}/`;

const headers = {
  'Authorization': `Bearer ${apiKey}`
};

const params = {
  'time': 1682290466,
  'formula': 'gfs.air_pressure',
  'min': 750,
  'max': 1100,
  'colors': '081030ff,002060ff,003492ff,005a94ff,007592ff,1a8c93ff,67a29bff,9bb7acff,b6b6b6ff,b0ae98ff,a7936bff,a37443ff,9f512cff,8e2f39ff,6f1840ff,300818ff',
  'offsets': '0.0,0.2777777777777778,0.4222222222222222,0.4777777777777778,0.5277777777777778,0.5666666666666667,0.5944444444444444,0.6180555555555556,0.6291666666666667,0.6402777777777777,0.6611111111111111,0.6888888888888889,0.7222222222222222,0.7666666666666667,0.8111111111111111,1.0'
};

axios.get(url, { headers, params, responseType: 'arraybuffer' })
  .then(response => {
    fs.writeFileSync('tile.png', response.data);
    console.log('Tile saved as tile.png');
  })
  .catch(error => {
    console.log('Error:', error);
  });
  

Example output (PNG)

This endpoint takes a Slippy Map Tile coordinate set (X, Y and Z, provided as URL parameters) and evaluates the given formula at every point given in the tile.

Depending on which format you select, certain parameters may be optional. For example, parameters relating to the color scale are only required if you ask for png, but is meaningless when requesting pfpng or csv.

HTTP Request

GET https://engine.stratisintel.com/map/{x}/{y}/{z}/

Parameters

Parameter Description
formula A formula expressed in the data language.
format The desired output format. Default: png. Allowed: png, pfpng and csv.
altitude The altitude to evaluate at, in meters.
time The time to evaluate at, in seconds.
colors Colors for the color scale. Required for format=png.
offsets Offsets for the color scale. Required for format=png.
max Maximum value for color scale. Defaults to 100.
min Minimum value for color scale. Defaults to 0.
interpolation Which interpolation method to use. Possible values: none, bilinear.

History

Introduced in v0.1.2.

Analyze a Program

  import requests

api_key = 'YOUR_API_KEY'
url = 'https://engine.stratisintel.com/analyze/'

headers = {
    'Authorization': f'Bearer {api_key}'
}

params = {
    'formula': 'gfs.air_pressure@[lat: -12.4637, lon:  130.8444] - gfs.air_pressure@[lat: -17.6509, lon: -149.4260]',
    'latitude': 64.3,
    'longitude': -22.3,
    'altitude': 2,
    'time': 1682290466
}

response = requests.get(url, headers=headers, params=params)

print(response.json())
  

Example output (JSON):

  {
    "engine": "Stratis Formula Engine v0.3.1",
    "formula": "gfs.air_pressure@[lat: -12.4637, lon:  130.8444] - gfs.air_pressure@[lat: -17.6509, lon: -149.4260]",
    "spacetime": {
        "latitude": 64.300003,
        "longitude": -22.299999,
        "altitude": 2,
        "time": 1682290466
    },
    "variables": [
        {
            "namespace": "gfs",
            "name": "air_pressure",
            "type": "SPATIOTEMPORAL",
            "source_type": "ES_DENSE",
            "description": "Air pressure in hPa."
        }
    ],
    "maps": [
        {
            "name": "gfs.air_pressure",
            "dhash": "a7aeba64d99f6dc115840c884dc89c134f91998efb1557332b07ac87ed74a4e4",
            "server": "data01.Stratis.is",
            "format": 1,
            "time_start": 1682283600,
            "time_end": 1682287200,
            "north": 90,
            "south": -90,
            "east": 179.75,
            "west": -180,
            "altitude": 0
        },
    ],
    "parse_successful": true,
    "typecheck_successful": true,
    "error": {
        "type": "NoError",
        "msg": ""
    },
    "result": 21.619995
}
  

This endpoint evaluates a single point at a given spacetime, but unlike /point/, it will give you detailed internal state information, exposing the engine’s reasoning, which should explain much of how the engine determined the final result was calculated in that spacetime point. This endpoint is primarily used for internal debugging purposes, but may occasionally be of use to other users.

HTTP Request

GET https://engine.stratisintel.com/analyze/

Parameters

Parameter Description
formula A formula expressed in the data language.
latitude The latitude to evaluate at, in decimal degrees.
longitude The longitude to evaluate at, in decimal degrees.
altitude The altitude to evaluate at, in meters.
time The time to evaluate at, in seconds.

Supported File Formats

CSV

Comma-separated value.

Solvers:

  • map

PNG

Standard PNG images.

Solvers:

  • map

PF-PNG

Packed-Float PNG files, or PF-PNG, are PNG images in RGBA mode with 8 bits per channel, where instead of pixels representing visual elements as traditionally with PNG, each pixel contains a packed 32 bit floating point number. The number corresponds to the output formula value in the given location.

This format is used as an intermediary format in some of Stratis’s systems, and can be produced by the Engine as an efficient way of communicating raw formula results for a large geographic area at once.

At present all PF-PNG images are produced in EPSG:3857 WebMercator projection, but this may change in the future.

In order to use the data from the file, you need to know:

  • What geographic area does the file cover?
    • You can determine this from your query
  • What units are the output data represented in?
    • You should be able to calculate this based on your input formula
  • What is the geographic stride of the pixels?
    • You can evaluate this by calculating the east-west and north-south ranges of your request, and dividing them by the width and height of the image respectively.

Solvers:

  • map

Python package

Currently you can only obtain the Python package by contacting us, info@stratisintel.com. It will be available on PyPI soon.

Prerequisites

Before you can use these API bindings, ensure you have:

  • Python 3.9 or later
  • An active Stratis account

Installation

Install the earthos-python package from PyPI using pip:

  pip install earthos
  

Or, if you have cloned this from a git repository, you can also:

  python setup.py install
  

(Note that this method is deprecated, but it will work for now.)

Getting started

First, log in to Stratis and generate an API key in your organization settings dialog.

Once you’ve got the package installed, you can import it and create an instance of the API.

  from earthos import EarthOS
APIKEY = 'your api key here'
eo = EarthOS(APIKEY)
  

With this in place, you can start fetching and working with the data, for example:

  myformula = EOVar('gfs.relative_humidity')/100.0
data = eo.get_region(north=90.0, south=-90.0, east=180.0, west=-180.0, formula=myformula)
data.show()
  

Examples and testing

You can use pytest to run tests to see if your setup is accurate. Beware that these tests will count against your API credits. See code in the tests and examples directories for further examples.

Errors

API queries that end in errors will at minimum provide an error code, and will typically further respond with a JSON message containing details about what went wrong.

Error Code Meaning
400 Bad Request – Your request is invalid.
401 Unauthorized – Your API key is wrong.
404 Not Found – The endpoint you asked for doesn’t exist.
405 Method Not Allowed – You tried to access an endpoint with an invalid method.
418 I’m a teapot.
429 Too Many Requests – You’ve exceeded your API quota.
500 Internal Server Error – We had a problem with our server. Try again later.
503 Service Unavailable – We’re temporarily offline for maintenance. Please try again later.