def query(self, range_subset=[], subsets={}, bbox=[], datetime_=None, format_='json'): """ Extract data from collection collection :param range_subset: list of bands :param subsets: dict of subset names with lists of ranges :param bbox: bounding box [minx,miny,maxx,maxy] :param datetime_: temporal (datestamp or extent) :param format_: data format of output :returns: coverage data as dict of CoverageJSON or native format """ bands = range_subset LOGGER.debug('Bands: {}, subsets: {}'.format(bands, subsets)) args = {'indexes': None} shapes = [] if all([not bands, not subsets, not bbox, format_ != 'json']): LOGGER.debug('No parameters specified, returning native data') return read_data(self.data) if all([ self._coverage_properties['x_axis_label'] in subsets, self._coverage_properties['y_axis_label'] in subsets, len(bbox) > 0 ]): msg = 'bbox and subsetting by coordinates are exclusive' LOGGER.warning(msg) raise ProviderQueryError(msg) if len(bbox) > 0: minx, miny, maxx, maxy = bbox crs_src = CRS.from_epsg(4326) if 'crs' in self.options: crs_dest = CRS.from_string(self.options['crs']) else: crs_dest = self._data.crs if crs_src == crs_dest: LOGGER.debug('source bbox CRS and data CRS are the same') shapes = [{ 'type': 'Polygon', 'coordinates': [[ [minx, miny], [minx, maxy], [maxx, maxy], [maxx, miny], [minx, miny], ]] }] else: LOGGER.debug('source bbox CRS and data CRS are different') LOGGER.debug('reprojecting bbox into native coordinates') t = Transformer.from_crs(crs_src, crs_dest, always_xy=True) minx2, miny2 = t.transform(minx, miny) maxx2, maxy2 = t.transform(maxx, maxy) LOGGER.debug('Source coordinates: {}'.format( [minx, miny, maxx, maxy])) LOGGER.debug('Destination coordinates: {}'.format( [minx2, miny2, maxx2, maxy2])) shapes = [{ 'type': 'Polygon', 'coordinates': [[ [minx2, miny2], [minx2, maxy2], [maxx2, maxy2], [maxx2, miny2], [minx2, miny2], ]] }] elif (self._coverage_properties['x_axis_label'] in subsets and self._coverage_properties['y_axis_label'] in subsets): LOGGER.debug('Creating spatial subset') x = self._coverage_properties['x_axis_label'] y = self._coverage_properties['y_axis_label'] shapes = [{ 'type': 'Polygon', 'coordinates': [[[subsets[x][0], subsets[y][0]], [subsets[x][0], subsets[y][1]], [subsets[x][1], subsets[y][1]], [subsets[x][1], subsets[y][0]], [subsets[x][0], subsets[y][0]]]] }] if bands: LOGGER.debug('Selecting bands') args['indexes'] = list(map(int, bands)) with rasterio.open(self.data) as _data: LOGGER.debug('Creating output coverage metadata') out_meta = _data.meta if self.options is not None: LOGGER.debug('Adding dataset options') for key, value in self.options.items(): out_meta[key] = value if shapes: # spatial subset try: LOGGER.debug('Clipping data with bbox') out_image, out_transform = rasterio.mask.mask( _data, filled=False, shapes=shapes, crop=True, indexes=args['indexes']) except ValueError as err: LOGGER.error(err) raise ProviderQueryError(err) out_meta.update({ 'driver': self.native_format, 'height': out_image.shape[1], 'width': out_image.shape[2], 'transform': out_transform }) else: # no spatial subset LOGGER.debug('Creating data in memory with band selection') out_image = _data.read(indexes=args['indexes']) if bbox: out_meta['bbox'] = [bbox[0], bbox[1], bbox[2], bbox[3]] elif shapes: out_meta['bbox'] = [ subsets[x][0], subsets[y][0], subsets[x][1], subsets[y][1] ] else: out_meta['bbox'] = [ _data.bounds.left, _data.bounds.bottom, _data.bounds.right, _data.bounds.top ] out_meta['units'] = _data.units LOGGER.debug('Serializing data in memory') with MemoryFile() as memfile: with memfile.open(**out_meta) as dest: dest.write(out_image) if format_ == 'json': LOGGER.debug('Creating output in CoverageJSON') out_meta['bands'] = args['indexes'] return self.gen_covjson(out_meta, out_image) else: # return data in native format LOGGER.debug('Returning data in native format') return memfile.read()
def test_read_data(): data = util.read_data(get_test_file_path('pygeoapi-test-config.yml')) assert isinstance(data, bytes)
def query(self, range_subset=[], subsets={}, format_='json'): """ Extract data from collection collection :param range_subset: list of data variables to return (all if blank) :param subsets: dict of subset names with lists of ranges :param format_: data format of output :returns: coverage data as dict of CoverageJSON or native format """ if not range_subset and not subsets and format_ != 'json': LOGGER.debug('No parameters specified, returning native data') return read_data(self.data) if len(range_subset) < 1: range_subset = self.fields data = self._data[[*range_subset]] if (self._coverage_properties['x_axis_label'] in subsets or self._coverage_properties['y_axis_label'] in subsets or self._coverage_properties['time_axis_label'] in subsets): LOGGER.debug('Creating spatio-temporal subset') query_params = {} for key, val in subsets.items(): if data.coords[key].values[0] > data.coords[key].values[-1]: LOGGER.debug('Reversing slicing low/high') query_params[key] = slice(val[1], val[0]) else: query_params[key] = slice(val[0], val[1]) LOGGER.debug('Query parameters: {}'.format(query_params)) try: data = data.sel(query_params) except Exception as err: LOGGER.warning(err) raise ProviderQueryError(err) if (any([ data.coords[self.x_field].size == 0, data.coords[self.y_field].size == 0 ])): msg = 'No data found' LOGGER.warning(msg) raise ProviderNoDataError(msg) out_meta = { 'bbox': [ data.coords[self.x_field].values[0], data.coords[self.y_field].values[0], data.coords[self.x_field].values[-1], data.coords[self.y_field].values[-1] ], "time": [ _to_datetime_string(data.coords[self.time_field].values[0]), _to_datetime_string(data.coords[self.time_field].values[-1]) ], "driver": "xarray", "height": data.dims[self.y_field], "width": data.dims[self.x_field], "time_steps": data.dims[self.time_field], "variables": {var_name: var.attrs for var_name, var in data.variables.items()} } LOGGER.debug('Serializing data in memory') if format_ == 'json': LOGGER.debug('Creating output in CoverageJSON') return self.gen_covjson(out_meta, data, range_subset) else: # return data in native format with tempfile.TemporaryFile() as fp: LOGGER.debug('Returning data in native format') fp.write(data.to_netcdf()) fp.seek(0) return fp.read()
def query(self, range_subset=[], subsets={}, bbox=[], datetime_=None, format_='json'): """ Extract data from collection collection :param range_subset: list of data variables to return (all if blank) :param subsets: dict of subset names with lists of ranges :param bbox: bounding box [minx,miny,maxx,maxy] :param datetime_: temporal (datestamp or extent) :param format_: data format of output :returns: coverage data as dict of CoverageJSON or native format """ if not range_subset and not subsets and format_ != 'json': LOGGER.debug('No parameters specified, returning native data') if format_ == 'zarr': return _get_zarr_data(self._data) else: return read_data(self.data) if len(range_subset) < 1: range_subset = self.fields data = self._data[[*range_subset]] if any([ self._coverage_properties['x_axis_label'] in subsets, self._coverage_properties['y_axis_label'] in subsets, self._coverage_properties['time_axis_label'] in subsets, datetime_ is not None ]): LOGGER.debug('Creating spatio-temporal subset') query_params = {} for key, val in subsets.items(): LOGGER.debug('Processing subset: {}'.format(key)) if data.coords[key].values[0] > data.coords[key].values[-1]: LOGGER.debug('Reversing slicing from high to low') query_params[key] = slice(val[1], val[0]) else: query_params[key] = slice(val[0], val[1]) if bbox: if all([ self._coverage_properties['x_axis_label'] in subsets, self._coverage_properties['y_axis_label'] in subsets, len(bbox) > 0 ]): msg = 'bbox and subsetting by coordinates are exclusive' LOGGER.warning(msg) raise ProviderQueryError(msg) else: query_params['x_axis_label'] = slice(bbox[0], bbox[2]) query_params['y_axis_label'] = slice(bbox[1], bbox[3]) if datetime_ is not None: if self._coverage_properties['time_axis_label'] in subsets: msg = 'datetime and temporal subsetting are exclusive' LOGGER.error(msg) raise ProviderQueryError(msg) else: if '/' in datetime_: begin, end = datetime_.split('/') if begin < end: query_params[self.time_field] = slice(begin, end) else: LOGGER.debug('Reversing slicing from high to low') query_params[self.time_field] = slice(end, begin) else: query_params[self.time_field] = datetime_ LOGGER.debug('Query parameters: {}'.format(query_params)) try: data = data.sel(query_params) except Exception as err: LOGGER.warning(err) raise ProviderQueryError(err) if (any([ data.coords[self.x_field].size == 0, data.coords[self.y_field].size == 0, data.coords[self.time_field].size == 0 ])): msg = 'No data found' LOGGER.warning(msg) raise ProviderNoDataError(msg) out_meta = { 'bbox': [ data.coords[self.x_field].values[0], data.coords[self.y_field].values[0], data.coords[self.x_field].values[-1], data.coords[self.y_field].values[-1] ], "time": [ _to_datetime_string(data.coords[self.time_field].values[0]), _to_datetime_string(data.coords[self.time_field].values[-1]) ], "driver": "xarray", "height": data.dims[self.y_field], "width": data.dims[self.x_field], "time_steps": data.dims[self.time_field], "variables": {var_name: var.attrs for var_name, var in data.variables.items()} } LOGGER.debug('Serializing data in memory') if format_ == 'json': LOGGER.debug('Creating output in CoverageJSON') return self.gen_covjson(out_meta, data, range_subset) elif format_ == 'zarr': LOGGER.debug('Returning data in native zarr format') return _get_zarr_data(data) else: # return data in native format with tempfile.TemporaryFile() as fp: LOGGER.debug('Returning data in native NetCDF format') fp.write(data.to_netcdf()) fp.seek(0) return fp.read()
def query(self, range_subset=[], subsets={}, format_='json'): """ Extract data from collection collection :param range_subset: list of bands :param subsets: dict of subset names with lists of ranges :param format_: data format of output :returns: coverage data as dict of CoverageJSON or native format """ bands = range_subset LOGGER.debug('Bands: {}, subsets: {}'.format(bands, subsets)) args = {'indexes': None} shapes = [] if not bands and not subsets and format_ != 'json': LOGGER.debug('No parameters specified, returning native data') return read_data(self.data) if (self._coverage_properties['x_axis_label'] in subsets and self._coverage_properties['y_axis_label'] in subsets): LOGGER.debug('Creating spatial subset') x = self._coverage_properties['x_axis_label'] y = self._coverage_properties['y_axis_label'] shapes = [{ 'type': 'Polygon', 'coordinates': [[[subsets[x][0], subsets[y][0]], [subsets[x][0], subsets[y][1]], [subsets[x][1], subsets[y][1]], [subsets[x][1], subsets[y][0]], [subsets[x][0], subsets[y][0]]]] }] if bands: LOGGER.debug('Selecting bands') args['indexes'] = list(map(int, bands)) with rasterio.open(self.data) as _data: LOGGER.debug('Creating output coverage metadata') out_meta = _data.meta if self.options is not None: LOGGER.debug('Adding dataset options') for key, value in self.options.items(): out_meta[key] = value if shapes: # spatial subset LOGGER.debug('Clipping data with bbox') out_image, out_transform = rasterio.mask.mask( _data, filled=False, shapes=shapes, crop=True, indexes=args['indexes']) out_meta.update({ 'driver': self.native_format, 'height': out_image.shape[1], 'width': out_image.shape[2], 'transform': out_transform }) else: # no spatial subset LOGGER.debug('Creating data in memory with band selection') out_image = _data.read(indexes=args['indexes']) if shapes: out_meta['bbox'] = [ subsets[x][0], subsets[y][0], subsets[x][1], subsets[y][1] ] else: out_meta['bbox'] = [ _data.bounds.left, _data.bounds.bottom, _data.bounds.right, _data.bounds.top ] out_meta['units'] = _data.units LOGGER.debug('Serializing data in memory') with MemoryFile() as memfile: with memfile.open(**out_meta) as dest: dest.write(out_image) if format_ == 'json': LOGGER.debug('Creating output in CoverageJSON') out_meta['bands'] = args['indexes'] return self.gen_covjson(out_meta, out_image) else: # return data in native format LOGGER.debug('Returning data in native format') return memfile.read()