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:
- An Ecosophy EarthOS account.
- An API key registered to your account.
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:
- Authorization
- Choosing a query endpoint
- Setting up the query parameters
- 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:
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 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:
- 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 |
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:
- colors ─ a comma separated list of hexadecimal RGBA colors.
- offsets ─ a comma separated list of numbers between 0 and 1.
- min ─ a minimum data value. Defaults to 0.
- max ─ a maximum data value. Defaults to 100.
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 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.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)
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)
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:
- All models are wrong; some are useful.
- Simple is better than complicated.
- Information is cheap, context is expensive.
Internals
Supported File Formats
CSV
Comma-separated value.
Solvers:
- map
- timeseries
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 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:
- 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
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:
- timeseries
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.