NAV
python julia go r javascript

EarthOS API

Ecosophy's EarthOS API allows you to query information about the state of Earth. It's a bit like an online database, which delivers solutions to spatiotemporal queries. The following documentation aims to guide you in its effective use.

If you're new here, check out the Getting Started guide first. It should set you up for success.

If you want to understand more about the design philosophy and internals, check out the Design Philosophy section.

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:

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. 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.earthos.ai
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 Data API 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.

Setting up the query parameters

In the Data API 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 Data API varies depending on the query you're making. Frequently it's one of these:

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

API bindings

Python package

Currently you can only obtain the Python package by contacting us, info@ecosophy.is. 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 EarthOS 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 python setup.py install (Note that this method is deprecated, but it will work for now.)

Getting started

First, log in to EarthOS 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. python 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.

Formula Language

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

Ecosophy’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 Data API 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:

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 Data API 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 Data API 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:

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:

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

Data API Reference

Ecosophy’s Data API is available at https://engine.earthos.ai/ . All URLS referenced in this section are relative to this path. The Data API is semi-stateless, so each request must contain all context that is relevant to the request. An exception is that statements in the formula language may contain meaningful references to stored data sets.

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

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

Color scales

Some request may require or allow you to provide a color scale (such as a map request with png format). In those cases, the endpoint will expects four additional parameters:

The number of colors and offsets must be equal. The offsets indicate where on the color scale the corresponding color will be placed.

Endpoint for generating a scale

GET /cs/

This endpoint is provided as a visualization convenience. It returns a PNG image of the requested height and width, representing the given color scale.

Parameter Description
colors Colors, as RGBA hexadecimal values.
offsets Color offsets.
min Minimum value for the color scale.
max Maximum value for the color scale.
width Pixel width of output image.
height Pixel height of output image.

Example color scales

Name Example Colors Offsets
Spectral 9e0142ff, d53e4fff, f46d43ff, fdae61ff, fee08bff, ffffbfff, e6f598ff, abdda4ff, 66c2a5ff, 3288bdff, 5e4fa2ff 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0
Red-Blue 67001fff, b2182bff, d6604dff, f4a582ff, fddbc7ff, f7f7f7ff, d1e5f0ff, 92c5deff, 4393c3ff, 2166acff, 053061ff 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0
Pink Yellow Green 8e0152ff, c51b7dff, de77aeff, f1b6daff, fde0efff, f7f7f7ff, e6f5d0ff, b8e186ff, 7fbc41ff, 4d9221ff, 276419ff 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0
Brown-Teal 543005ff, 8c510aff, bf812dff, dfc27dff, f6e8c3ff, f5f5f5ff, c7eae5ff, 80cdc1ff, 35978fff, 01665eff, 003c30ff 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0
Purple-Orange 7f3b08ff, b35806ff, e08214ff, fdb863ff, fee0b6ff, f7f7f7ff, d8daebff, b2abd2ff, 8073acff, 542788ff, 2d004bff 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0
Set2 66c2a5ff, fc8d62ff, 8da0cbff, e78ac3ff, a6d854ff, ffd92fff, e5c494ff, b3b3b3ff 0.0, 0.14285714285714285, 0.2857142857142857, 0.42857142857142855, 0.5714285714285714, 0.7142857142857143, 0.8571428571428571, 1.0
Accent 7fc97fff, beaed4ff, fdc086ff, ffff99ff, 386cb0ff, f0027fff, bf5b17ff, 666666ff 0.0, 0.14285714285714285, 0.2857142857142857, 0.42857142857142855, 0.5714285714285714, 0.7142857142857143, 0.8571428571428571, 1.0
Set1 e41a1cff, 377eb8ff, 4daf4aff, 984ea3ff, ff7f00ff, ffff33ff, a65628ff, f781bfff, 999999ff 0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0
Set3 8dd3c7ff, ffffb3ff, bebadaff, fb8072ff, 80b1d3ff, fdb462ff, b3de69ff, fccde5ff, d9d9d9ff, bc80bdff, ccebc5ff, ffed6fff 0.0, 0.09090909090909091, 0.18181818181818182, 0.2727272727272727, 0.36363636363636365, 0.45454545454545453, 0.5454545454545454, 0.6363636363636364, 0.7272727272727273, 0.8181818181818182, 0.9090909090909091, 1.0

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.earthos.ai/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.earthos.ai/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.earthos.ai/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.earthos.ai/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": "Ecosophy 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.earthos.ai/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.

History

Introduced in v0.2.3

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.earthos.ai/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.earthos.ai/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.earthos.ai/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.earthos.ai/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": "Ecosophy 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 this case, it's probably better to use one Points query, rather than many consecutive Point queries. Main reasons:

HTTP Request

POST https://engine.earthos.ai/points/

History

Introduced in v0.2.8

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.earthos.ai/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.earthos.ai/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.earthos.ai/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.earthos.ai/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)

Example output

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.earthos.ai/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)

History

Introduced in v0.1.2.

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.earthos.ai/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.earthos.ai/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.earthos.ai/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.earthos.ai/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)

Example output

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.earthos.ai/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.

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.earthos.ai/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.earthos.ai/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.earthos.ai/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.earthos.ai/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 Data API.

HTTP Request

GET https://engine.earthos.ai/variables/

History

Introduced in v0.2.7.

Analyze a Program

import requests

api_key = 'YOUR_API_KEY'
url = 'https://engine.earthos.ai/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": "Ecosophy 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.ecosophy.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.earthos.ai/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.

History

Introduced in v0.1.2

Design Philosophy

At Ecosophy, we are trying to turn planet-sized data problems into planet-scale management solutions. We work with petabyte-scale data describing innumerous factors relating to our planet -- environmental and socioeconomic; forecast and measured; continuous and sparse; in the past, present and future. But despite the vastness of the underlying data, we believe that most questions should be answered in way less than a second.

As our core technology, Ecosophy's Data API is built around a few core beliefs, which then expand into a number of design decisions:

Internals

Supported File Formats

CSV

Comma-separated value.

Solvers:

PNG

Standard PNG images.

Solvers:

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 Ecosophy’s systems, and can be produced by the Data API 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:

Solvers:

BTS (Binary Time Series)

Binary Time Series files, or BTS, are compressed files containing an efficient transmission method for constant-timestep time series. This format is specified in the BTS Format Specification, which can be obtained by emailing info@ecosophy.is .

Solvers:

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.

Copyright

This documentation and the Ecosophy Data API are Copyright © Ecosophy ehf. 2023.

Contact

Feel free to get in touch with us if you have any questions, comments or feedback.