# Coordinates¶

## Overview¶

Coordinates are used to:

1. Evaluate nodes which retrieve and process data

2. Define the coordinates of data sources

Podpac Coordinates are modeled after the coords in xarray, with some additional restrictions and enhancements. Coordinates are created from a list of coordinate `values` and a corresponding list of `dims`:

```podpac.Coordinates(values, dims=dims, ...)
```

Unlike xarray, podpac coordinate values are always either `float` or `np.datetime64`. For convenience, podpac automatically converts datetime strings such as `'2018-01-01'` to `np.datetime64`. In addition, the allowed dimensions are `'lat'`, `'lon'`, `'time'`, and `'alt'`.

## Coordinate Creation¶

### Unstacked Coordinates¶

Unstacked multidimensional coordinates form a grid of points. For example, the following Coordinates contain three dimensions and a total of 24 points.

```>>> lat = [0, 1, 2]
>>> lon = [10, 20, 30, 40]
>>> time = ['2018-01-01', '2018-01-02']
>>> Coordinates([lat, lon], dims=['lat', 'lon'])
Coordinates
lat: ArrayCoordinates1d(lat): Bounds[0.0, 2.0], N, ctype['midpoint']
lon: ArrayCoordinates1d(lon): Bounds[10.0, 40.0], N, ctype['midpoint']
>>> Coordinates([lat, lon, time], dims=['lat', 'lon', 'time'])
Coordinates
lat: ArrayCoordinates1d(lat): Bounds[0.0, 2.0], N, ctype['midpoint']
lon: ArrayCoordinates1d(lon): Bounds[10.0, 40.0], N, ctype['midpoint']
time: ArrayCoordinates1d(time): Bounds[2018-01-01, 2018-01-02], N, ctype['midpoint']
```

You can also create coordinates with just one dimension the same way:

```>>> Coordinates([time], dims=['time'])
Coordinates
time: ArrayCoordinates1d(time): Bounds[2018-01-01, 2018-01-02], N, ctype['midpoint']
```

### Stacked Coordinates¶

Coordinates from multiple dimensions can be stacked together in a list (rather than representing a grid).

For example, Coordinates with stacked latitude and longitude contain one point for each (lat, lon) pair. Note that the name for this stacked dimension is ‘lat_lon’, using an underscore to combine the underlying dimensions. The following example has a single stacked dimension and a total of 3 points.

```>>> lat = [0, 1, 2]
>>> lon = [10, 20, 30]
>>> c = Coordinates([[lat, lon]], dims=['lat_lon'])
>>> c
Coordinates
lat_lon[lat]: ArrayCoordinates1d(lat): Bounds[0.0, 2.0], N, ctype['midpoint']
lat_lon[lon]: ArrayCoordinates1d(lon): Bounds[10.0, 30.0], N, ctype['midpoint']
>>> c['lat_lon'].coordinates
(0.0, 10.0)
```

Coordinates can combine stacked dimensions and unstacked dimensions. For example, in the following Coordinates the `(lat, lon)` values and the `time` values form a grid of 6 total points.

```>>> lat = [0, 1, 2]
>>> lon = [10, 20, 30]
>>> time = ['2018-01-01', '2018-01-02']
>>> c = Coordinates([[lat, lon], time], dims=['lat_lon', 'time'])
Coordinates
lat_lon[lat]: ArrayCoordinates1d(lat): Bounds[0.0, 2.0], N, ctype['midpoint']
lat_lon[lon]: ArrayCoordinates1d(lon): Bounds[10.0, 30.0], N, ctype['midpoint']
time: ArrayCoordinates1d(time): Bounds[2018-01-01, 2018-01-02], N, ctype['midpoint']
>>> c['lat_lon'].coordinates
(0.0, 10.0)
>>> c['time'].coordinates
numpy.datetime64('2018-01-01')
```

### Uniformly-Spaced Coordinates¶

Podpac provides two convenience functions `crange` and `clinspace` for creating uniformly-spaced coordinates, similar to the `arange` and `linspace` functions provided by numpy.

Coordinates Range

`podpac.crange` creates uniformly-spaced coordinates from a start, stop, and step.

Unlike `np.arange`:

• string inputs are supported for datetimes and timedeltas

• the stop value will be included in the coordinates if it falls an exact number of steps from the start

```>>> c = podpac.crange(0, 7, 2)
>>> c.coordinates
array([0., 2., 4., 6.])
```
```>>> c = podpac.crange(0, 8, 2)
>>> c.coordinates
array([0., 2., 4., 6., 8.])
```
```>>> c = podpac.crange('2018-01-01', '2018-03-01', '1,M')
>>> c.coordinates
array(['2018-01-01', '2018-02-01', '2018-03-01'], dtype='datetime64[D]')
```

Coordinates Linspace

`podpac.clinspace` creates uniformly-spaced coordinates from a start, stop, and size.

Unlike `np.linspace`:

• string inputs are supported for datetimes

• tuple inputs are supported for stacked coordinates

```>>> c = podpac.clinspace(0, 8, 5)
>>> c.coordinates
array([0., 2., 4., 6., 8.])
```
```>>> c.coordinates
array(['2018-01-01', '2018-01-30', '2018-02-28'], dtype='datetime64[D]')
```
```>>> c = podpac.clinspace((0, 10), (1, 20), 3)
>>> c.coordinates
MultiIndex(levels=[[0.0, 0.5, 1.0], [10.0, 15.0, 20.0]],
labels=[[0, 1, 2], [0, 1, 2]])
```

These functions wrap UniformCoordinates1d (see Advanced Usage), which is particularly useful for coordinates with an extremely large number of points.

TODO

TODO ctype, etc

### Alternate Constructors¶

Unstacked coordinates can also be created using the `Coordinates.grid` alternate constructor:

```>>> Coordinates.grid(lat=[0, 1, 2], lon=[10, 20, 30, 40])
Coordinates
lat: ArrayCoordinates1d(lat): Bounds[0.0, 2.0], N, ctype['midpoint']
lon: ArrayCoordinates1d(lon): Bounds[10.0, 40.0], N, ctype['midpoint']
```

Stacked coordinates can be created using the `Coordinates.points` alternate constructor:

```>>> Coordinates.points(lat=[0, 1, 2], lon=[10, 20, 30])
Coordinates
lat_lon[lat]: ArrayCoordinates1d(lat): Bounds[0.0, 2.0], N, ctype['midpoint']
lat_lon[lon]: ArrayCoordinates1d(lon): Bounds[10.0, 30.0], N, ctype['midpoint']
```

For convenience, a tuple can be used to generate uniformly-spaced coordinates. If the third item is an integer, it is interpreted as a size, otherwise it is interpreted as a step. The following will all be equivalent:

```Coordinates.grid(lat=(0, 2, 3), lon=(10, 40, 4))
Coordinates.grid(lat=(0, 2, 1.0), lon=(10, 40, 10.0))
Coordinates.grid(lat=clinspace(0, 2, 3), lon=clinspace(10, 40, 4))
Coordinates.grid(lat=crange(0, 2, 1), lon=crange(10, 40, 10))
```

Note that in Python 3.5 and below, the `order` argument is required to both `grid` and `points`

```Coordinates.grid(lat=[0, 1, 2], lon=[10, 20, 30], order=['lat', 'lon'])
Coordinates.points(lat=[0, 1, 2], lon=[10, 20, 30], order=['lat', 'lon'])
```

TODO

```>>> lat = UniformCoordinates1d(0, 1, size=100, name='lat')
>>> lon = UniformCoordinates1d(10, 20, size=100, name='lon')
>>> time = ArrayCoordinates1d(['2018-01-01', '2018-02-03'], name='time')
>>> Coordinates([StackedCoordinates([lat, lon]), time])
Coordinates
lat_lon[lat]: UniformCoordinates1d(lat): Bounds[0.0, 1.0], N, ctype['midpoint']
lat_lon[lon]: UniformCoordinates1d(lon): Bounds[10.0, 20.0], N, ctype['midpoint']
time: ArrayCoordinates1d(time): Bounds[2018-01-01, 2018-02-03], N, ctype['midpoint']
```

TODO mixed ctypes, etc…

## Coordinate API¶

TODO

Coordinates contain some useful properties relating to its dimensions and underlying coordinate values.

```>>> c = Coordinates([lat, lon, time], dims=['lat', 'lon', 'time'])
>>> c.ndims
>>> c.dims
>>> c.shape
>>> c.size
```

Coordinates are dict-like. The `keys()`, `values()`, and `items()`

TODO