def test_append(ax0, ax1): # test copy/ref behaviour when appending an object axes = Axes() axes.append(ax0) assert ax0 is axes[0] axes.append(ax1) assert ax1 is axes[1]
def __init__(self, values=None, axes=None, time=None, lat=None, lon=None, **kwargs): """ """ keyword = (time is not None or lat is not None or lon is not None) assert not (axes is not None and keyword), "can't input both `axes=` and keyword arguments!" # construct the axes if keyword: axes = Axes() if time is not None: axes.append(Axis(time, 'time')) if lat is not None: axes.append(Axis(lat, 'lat')) if lon is not None: axes.append(Axis(lon, 'lon')) super(GeoArray, self).__init__(values, axes, **kwargs) # order dimensions # add weight on latitude for ax in self.axes: if is_latitude(ax.name): ax.weights = lambda x: np.cos(np.radians(x)) if is_longitude(ax.name): ax.modulo = 360
def getaxes_broadcast(obj, indices): """ broadcast array-indices & integers, numpy's classical Examples -------- >>> import dimarray as da >>> a = da.zeros(shape=(3,4,5,6)) >>> a.take((slice(None),[0, 1],slice(None),2), broadcast=True).shape (2, 3, 5) >>> a.take((slice(None),[0, 1],2,slice(None)), broadcast=True).shape (3, 2, 6) """ from dimarray import Axis, Axes # new axes: broacast indices (should do the same as above, since integers are just broadcast) indices2 = broadcast_indices(indices) # assert np.all(newval == obj.values[indices2]) # make a multi-axis with tuples is_array2 = np.array([np.iterable(ix) for ix in indices2]) nb_array2 = is_array2.sum() # If none or one array is present, easy if nb_array2 <= 1: newaxes = [obj.axes[i][ix] for i, ix in enumerate(indices) if not np.isscalar(ix)] # indices or indices2, does not matter # else, finer check needed else: # same stats but on original indices is_array = np.array([np.iterable(ix) for ix in indices]) array_ix_pos = np.where(is_array)[0] # Determine where the axis will be inserted # - need to consider the integers as well (broadcast as arrays) # - if two indexed dimensions are not contiguous, new axis placed at first position... # obj = zeros((3,4,5,6)) # obj[:,[1,2],:,0].shape ==> (2, 3, 5) # obj[:,[1,2],0,:].shape ==> (3, 2, 6) array_ix_pos2 = np.where(is_array2)[0] if np.any(np.diff(array_ix_pos2) > 1): # that mean, if two indexed dimensions are not contiguous insert = 0 else: insert = array_ix_pos2[0] # Now determine axis value # ...if originally only one array was provided, use these values correspondingly if len(array_ix_pos) == 1: i = array_ix_pos[0] values = obj.axes[i].values[indices[i]] name = obj.axes[i].name # ...else use a list of tuples else: values = list(zip(*[obj.axes[i].values[indices2[i]] for i in array_ix_pos])) name = ",".join([obj.axes[i].name for i in array_ix_pos]) broadcastaxis = Axis(values, name) newaxes = Axes() for i, ax in enumerate(obj.axes): # axis is already part of the broadcast axis: skip if is_array2[i]: continue else: newaxis = ax[indices2[i]] ## do not append axis if scalar #if np.isscalar(newaxis): # continue newaxes.append(newaxis) # insert the right new axis at the appropriate position newaxes.insert(insert, broadcastaxis) return newaxes
def __init__( self, values=None, axes=None, time=None, z=None, y=None, x=None, lat=None, lon=None, dims=None, standard_name=None, long_name=None, units=None, **kwargs ): """ Parameters ---------- values : array-like axes : list of array-like or Axis objects, optional time, z, y, x, lat, lon: spatiotemporal coordinates, optional These keyword arguments are provided for convenience. They can be used instead of (but not in addition to) the `axes` parameter. See notes below about implied array shape. dims : sequence, optional sequence of axis names (dimensions) This is needed when axes are defined via keywords but array-shape does not conform with CF-conventions. standard_name, long_name, units : str, optional standard attributes according to the CF-1.4 netCDF conventions. Will be added to metadata. **kwargs : keyword arguments, optional Passed to dimarray.DimArray Notes ----- When coordinate axes are passed via keyword arguments it is assumed that the array shape follows the CF-conventions: time, vertical coordinate (z), northing coordinate (y or lat), easting coordinate (x or lon). If it is not the case, please indicate the `dims` parameters or pass axes via the `axes=` parameter instead of keyword arguments. See Also -------- dimarray.DimArray : base class with no "geo" specificities dimarray.geo.Coordinate : base class for geo-axes dimarray.geo.Time dimarray.geo.Latitude, dimarray.geo.Longitude dimarray.geo.Z, dimarray.geo.Y, dimarray.geo.X """ keyword = ( time is not None or lat is not None or lon is not None or x is not None or y is not None or z is not None ) if axes is not None and keyword: msg = "Axes can be provided EITHER via `axes=` OR keyword arguments" raise ValueError(msg) # construct the axes if keyword: axes = Axes() if time is not None: axes.append(Time(time, "time")) if z is not None: axes.append(Z(z, "z")) if y is not None: axes.append(Y(y, "y")) if lat is not None: axes.append(Latitude(lat, "lat")) if x is not None: axes.append(X(x, "x")) if lon is not None: axes.append(Longitude(lon, "lon")) if dims is not None: if len(dims) != len(axes): msg = "dims ({}) and axes ({}) lengths \ do not match".format( len(dims), len(axes) ) raise ValueError(msg) axes = [axes[nm] for nm in dims] # if metadata is not None: # for k in metadata.keys(): # assert k not in kwargs, "{} was provided multiple times".format(k) # kwargs.update(metadata) # TODO: make metadata a parameter in DimArray as well self._grid_mapping = None super(GeoArray, self).__init__(values, axes, **kwargs) # order dimensions # add metadata if units: self.units = units if standard_name: self.standard_name = standard_name if long_name: self.long_name = long_name # Do some guessing to define coordinates for i, ax in enumerate(self.axes): if isinstance(ax, Coordinate): continue elif is_latitude(ax.name) or (hasattr(ax, "standard_name") and ax.standard_name == "latitude"): self.axes[i] = Latitude.from_axis(ax) elif is_longitude(ax.name) or (hasattr(ax, "standard_name") and ax.standard_name == "longitude"): self.axes[i] = Longitude.from_axis(ax) elif is_time(ax.name): self.axes[i] = Time.from_axis(ax) # 'x', 'y', 'z' are too general to be used. elif ax.name == "x" or (hasattr(ax, "standard_name") and ax.standard_name == "projection_x_coordinate"): self.axes[i] = X.from_axis(ax) elif ax.name == "y" or (hasattr(ax, "standard_name") and ax.standard_name == "projection_y_coordinate"): self.axes[i] = Y.from_axis(ax) elif ax.name in ("z", "height", "depth"): self.axes[i] = Z.from_axis(ax) # Check unicity of coordinates. time_coords = filter(lambda ax: isinstance(ax, Time), self.axes) x_coords = filter(lambda ax: isinstance(ax, X), self.axes) y_coords = filter(lambda ax: isinstance(ax, Y), self.axes) z_coords = filter(lambda ax: isinstance(ax, Z), self.axes) if len(time_coords) > 1: raise ValueError("More than one time coordinate found") if len(x_coords) > 1: raise ValueError("More than one x coordinate found") if len(y_coords) > 1: raise ValueError("More than one y coordinate found") if len(z_coords) > 1: raise ValueError("More than one z coordinate found")