def _process_geometries_(self,itr,field,headers,value_keys,alias): ''' :param sequence itr: Contains geometry dictionaries to process. If there are no geometries to process, this will be a sequence of one element with an empty dictionary. :param :class:`ocgis.interface.Field` field: The field object to use for operations. :param sequence headers: Sequence of strings to use as headers for the creation of the collection. :param sequence value_keys: Sequence of strings to use as headers for the keyed output functions. :param str alias: The request data alias currently being processed. :yields: :class:~`ocgis.SpatialCollection` ''' ## loop over the iterator for gd in itr: ## always work with a new geometry dictionary gd = deepcopy(gd) ## CFRotatedPole takes special treatment. only do this if a subset ## geometry is available. this variable is needed to determine if ## backtransforms are necessary. original_rotated_pole_crs = None if isinstance(field.spatial.crs,CFRotatedPole): ## only transform if there is a subset geometry if len(gd) > 0: ## store row and column dimension metadata and names before ## transforming as this information is lost w/out row and ## column dimensions on the transformations. original_row_column_metadata = {'row':{'name':field.spatial.grid.row.name, 'meta':field.spatial.grid.row.meta}, 'col':{'name':field.spatial.grid.col.name, 'meta':field.spatial.grid.col.meta}} ## reset the geometries field.spatial._geom = None ## get the new grid dimension field.spatial.grid = get_rotated_pole_spatial_grid_dimension(field.spatial.crs,field.spatial.grid) ## update the CRS. copy the original CRS for possible later ## transformation back to rotated pole. original_rotated_pole_crs = deepcopy(field.spatial.crs) field.spatial.crs = CFWGS84() ## initialize the collection object to store the subsetted data. if ## the output CRS differs from the field's CRS, adjust accordingly ## when initializing. if self.ops.output_crs is not None and field.spatial.crs != self.ops.output_crs: collection_crs = self.ops.output_crs else: collection_crs = field.spatial.crs coll = SpatialCollection(crs=collection_crs,headers=headers,meta=gd.get('meta'), value_keys=value_keys) ## reference variables from the geometry dictionary geom = gd.get('geom') ## keep this around for the collection creation coll_geom = deepcopy(geom) crs = gd.get('crs') ## if there is a spatial abstraction, ensure it may be loaded. if self.ops.abstraction is not None: try: getattr(field.spatial.geom,self.ops.abstraction) except ImproperPolygonBoundsError: exc = ImproperPolygonBoundsError('A "polygon" spatial abstraction is not available without the presence of bounds.') ocgis_lh(exc=exc,logger='subset') except Exception as e: ocgis_lh(exc=e,logger='subset') ## if there is a snippet, return the first realization, time, and level if self.ops.snippet: field = field[0,0,0,:,:] ## if there is a slice, use it to subset the field. elif self.ops.slice is not None: field = field.__getitem__(self.ops.slice) ## see if the selection crs matches the field's crs if crs is not None and crs != field.spatial.crs: geom = project_shapely_geometry(geom,crs.sr,field.spatial.crs.sr) crs = field.spatial.crs ## if the geometry is a point, we need to buffer it... if type(geom) in [Point,MultiPoint]: ocgis_lh(logger=self._subset_log,msg='buffering point geometry',level=logging.DEBUG) geom = geom.buffer(self.ops.search_radius_mult*field.spatial.grid.resolution) ## update the geometry to store in the collection coll_geom = deepcopy(geom) ## get the ugid following geometry manipulations if 'properties' in gd and 'UGID' in gd['properties']: ugid = gd['properties']['UGID'] else: ugid = 1 if geom is None: msg = 'No selection geometry. Returning all data. Assiging UGID as 1.' else: msg = 'Subsetting with selection geometry having UGID={0}'.format(ugid) ocgis_lh(msg=msg,logger=self._subset_log) ## check for unique ugids. this is an issue with point subsetting ## as the buffer radius changes by dataset. if ugid in self._ugid_unique_store and geom is not None: ## only update if the geometry is unique if not any([__.almost_equals(geom) for __ in self._geom_unique_store]): prev_ugid = ugid ugid = max(self._ugid_unique_store) + 1 self._ugid_unique_store.append(ugid) msg = 'Updating UGID {0} to {1} to maintain uniqueness.'.format(prev_ugid,ugid) ocgis_lh(msg,self._subset_log,level=logging.WARN,alias=alias,ugid=ugid) else: self._geom_unique_store.append(geom) else: self._ugid_unique_store.append(ugid) self._geom_unique_store.append(geom) ## try to update the properties try: gd['properties']['UGID'] = ugid except KeyError: if not isinstance(gd,dict): raise ## unwrap the data if it is geographic and 360 if geom is not None and crs == CFWGS84(): if CFWGS84.get_is_360(field.spatial): ocgis_lh('unwrapping selection geometry',self._subset_log,alias=alias,ugid=ugid,level=logging.DEBUG) geom = Wrapper().unwrap(geom) ## perform the spatial operation if geom is not None: try: if self.ops.spatial_operation == 'intersects': sfield = field.get_intersects(geom, use_spatial_index=env.USE_SPATIAL_INDEX, select_nearest=self.ops.select_nearest) elif self.ops.spatial_operation == 'clip': sfield = field.get_clip(geom, use_spatial_index=env.USE_SPATIAL_INDEX, select_nearest=self.ops.select_nearest) else: ocgis_lh(exc=NotImplementedError(self.ops.spatial_operation)) except EmptySubsetError as e: if self.ops.allow_empty: ocgis_lh(alias=alias,ugid=ugid,msg='empty geometric operation but empty returns allowed',level=logging.WARN) sfield = None else: msg = str(e) + ' This typically means the selection geometry falls outside the spatial domain of the target dataset.' ocgis_lh(exc=ExtentError(message=msg),alias=alias,logger=self._subset_log) else: sfield = field ## if the base size is being requested, bypass the rest of the ## operations. if self._request_base_size_only == False: ## if empty returns are allowed, there be an empty field if sfield is not None: ## aggregate if requested if self.ops.aggregate: ocgis_lh('executing spatial average',self._subset_log,alias=alias,ugid=ugid) sfield = sfield.get_spatially_aggregated(new_spatial_uid=ugid) ## wrap the returned data. if not env.OPTIMIZE_FOR_CALC: if CFWGS84.get_is_360(sfield.spatial): if self.ops.output_format != 'nc' and self.ops.vector_wrap: ocgis_lh('wrapping output geometries',self._subset_log,alias=alias,ugid=ugid, level=logging.DEBUG) ## modifying these values in place will change the values ## in the base field. a copy is necessary. sfield.spatial = deepcopy(sfield.spatial) sfield.spatial.crs.wrap(sfield.spatial) ## check for all masked values if env.OPTIMIZE_FOR_CALC is False and self.ops.file_only is False: for variable in sfield.variables.itervalues(): ocgis_lh(msg='Fetching data for variable with alias "{0}".'.format(variable.alias), logger=self._subset_log) if variable.value.mask.all(): ## masked data may be okay depending on other opeartional ## conditions. if self.ops.snippet or self.ops.allow_empty or (self.ops.output_format == 'numpy' and self.ops.allow_empty): if self.ops.snippet: ocgis_lh('all masked data encountered but allowed for snippet', self._subset_log,alias=alias,ugid=ugid,level=logging.WARN) if self.ops.allow_empty: ocgis_lh('all masked data encountered but empty returns allowed', self._subset_log,alias=alias,ugid=ugid,level=logging.WARN) if self.ops.output_format == 'numpy': ocgis_lh('all masked data encountered but numpy data being returned allowed', logger=self._subset_log,alias=alias,ugid=ugid,level=logging.WARN) else: ## if the geometry is also masked, it is an empty spatial ## operation. if sfield.spatial.abstraction_geometry.value.mask.all(): ocgis_lh(exc=EmptyData,logger=self._subset_log) ## if none of the other conditions are met, raise the masked data error else: ocgis_lh(logger=self._subset_log,exc=MaskedDataError(),alias=alias,ugid=ugid) ## transform back to rotated pole if necessary if original_rotated_pole_crs is not None: if self.ops.output_crs is None and not isinstance(self.ops.output_crs,CFWGS84): # copy the spatial mask to the new spatial array spatial_mask_before_transform = deepcopy(sfield.spatial.get_mask()) # need to load the values before proceeding. source indices will disappear. for variable in sfield.variables.itervalues(): variable.value # reset the geometries sfield.spatial._geom = None sfield.spatial.grid = get_rotated_pole_spatial_grid_dimension( original_rotated_pole_crs,sfield.spatial.grid,inverse=True, rc_original=original_row_column_metadata) # update the grid mask with the previous spatial mask sfield.spatial.grid.value.mask = spatial_mask_before_transform ## update the uid mask to match the spatial mask sfield.spatial.uid = np.ma.array(sfield.spatial.uid,mask=spatial_mask_before_transform) sfield.spatial.crs = original_rotated_pole_crs ## update the coordinate system of the data output if self.ops.output_crs is not None: ## if the geometry is not None, it may need to be projected to match ## the output crs. if geom is not None and crs != self.ops.output_crs: geom = project_shapely_geometry(geom,crs.sr,self.ops.output_crs.sr) coll_geom = deepcopy(geom) ## update the coordinate reference system of the spatial ## dimension. try: sfield.spatial.update_crs(self.ops.output_crs) ## this is likely a rotated pole origin except RuntimeError as e: if isinstance(sfield.spatial.crs,CFRotatedPole): assert(isinstance(self.ops.output_crs,WGS84)) sfield.spatial._geom = None sfield.spatial.grid = get_rotated_pole_spatial_grid_dimension( sfield.spatial.crs,sfield.spatial.grid) sfield.spatial.crs = self.ops.output_crs else: ocgis_lh(exc=e,logger=self._subset_log) ## the geometry may need to be wrapped or unwrapped depending on ## the vector wrap situation name = alias if sfield is None else None coll.add_field(ugid, coll_geom, sfield, properties=gd.get('properties'), name=name) yield(coll)
def _process_geometries_(self,rds): ocgis_lh(msg='entering _process_geometries_',logger=self._subset_log,level=logging.DEBUG) ## select headers if self.ops.headers is not None: headers = self.ops.headers else: if self.cengine is not None: if self.cengine._check_calculation_members_(self.cengine.funcs,AbstractMultivariateFunction): headers = constants.multi_headers else: headers = constants.calc_headers else: headers = constants.raw_headers ## keyed output functions require appending headers regardless. there is ## only one keyed output function allowed in a request. if self.cengine is not None: if self.cengine._check_calculation_members_(self.cengine.funcs,AbstractKeyedOutputFunction): value_keys = self.cengine.funcs[0]['ref'].structure_dtype['names'] headers = list(headers) + value_keys ## remove the 'value' attribute headers as this is replaced by the ## keyed output names. try: headers.remove('value') ## it may not be in the list because of a user overload except ValueError: pass else: value_keys = None else: value_keys = None alias = '_'.join([r.alias for r in rds]) ocgis_lh('processing...',self._subset_log,alias=alias) ## return the field object try: field = [rd.get(format_time=self.ops.format_time) for rd in rds] if len(field) > 1: field[0].variables.add_variable(field[1].variables.first()) field = field[0] except EmptySubsetError as e: if self.ops.allow_empty: ocgis_lh(msg='time or level subset empty but empty returns allowed', logger=self._subset_log,level=logging.WARN) coll = SpatialCollection(headers=headers) coll.add_field(1,None,rd.alias,None) try: yield(coll) finally: return else: ocgis_lh(exc=ExtentError(message=str(e)),alias=rd.alias,logger=self._subset_log) ## set iterator based on presence of slice. slice always overrides geometry. if self.ops.slice is not None: itr = [{}] else: itr = [{}] if self.ops.geom is None else self.ops.geom ## loop over the iterator for gd in itr: ## initialize the collection object to store the subsetted data. if ## the output CRS differs from the field's CRS, adjust accordingly ## when initilizing. if self.ops.output_crs is not None and field.spatial.crs != self.ops.output_crs: collection_crs = self.ops.output_crs else: collection_crs = field.spatial.crs coll = SpatialCollection(crs=collection_crs,headers=headers,meta=gd.get('meta'), value_keys=value_keys) ## reference variables from the geometry dictionary geom = gd.get('geom') crs = gd.get('crs') if 'properties' in gd and 'UGID' in gd['properties']: ugid = gd['properties']['UGID'] else: ## try to get lowercase ugid in case the shapefile is not perfectly ## formed. however, if there is no geometry accept the error and ## use the default geometry identifier. if len(gd) == 0: ugid = 1 else: ugid = gd['properties']['ugid'] ocgis_lh('processing',self._subset_log,level=logging.DEBUG,alias=alias,ugid=ugid) ## if there is a spatial abstraction, ensure it may be loaded. if self.ops.abstraction is not None: try: getattr(field.spatial.geom,self.ops.abstraction) except ImproperPolygonBoundsError: exc = ImproperPolygonBoundsError('A "polygon" spatial abstraction is not available without the presence of bounds.') ocgis_lh(exc=exc,logger='subset') except Exception as e: ocgis_lh(exc=e,logger='subset') ## if there is a snippet, return the first realization, time, and level if self.ops.snippet: field = field[0,0,0,:,:] ## if there is a slice, use it to subset the field. elif self.ops.slice is not None: field = field.__getitem__(self.ops.slice) ## see if the selection crs matches the field's crs if crs is not None and crs != field.spatial.crs: geom = project_shapely_geometry(geom,crs.sr,field.spatial.crs.sr) crs = field.spatial.crs ## if the geometry is a point, we need to buffer it... if type(geom) in [Point,MultiPoint]: ocgis_lh(logger=self._subset_log,msg='buffering point geometry',level=logging.DEBUG) geom = geom.buffer(self.ops.search_radius_mult*field.spatial.grid.resolution) ## unwrap the data if it is geographic and 360 if geom is not None and crs == CFWGS84(): if CFWGS84.get_is_360(field.spatial): ocgis_lh('unwrapping selection geometry',self._subset_log,alias=alias,ugid=ugid) geom = Wrapper().unwrap(geom) ## perform the spatial operation if geom is not None: try: if self.ops.spatial_operation == 'intersects': sfield = field.get_intersects(geom) elif self.ops.spatial_operation == 'clip': sfield = field.get_clip(geom) else: ocgis_lh(exc=NotImplementedError(self.ops.spatial_operation)) except EmptySubsetError as e: if self.ops.allow_empty: ocgis_lh(alias=alias,ugid=ugid,msg='empty geometric operation but empty returns allowed',level=logging.WARN) sfield = None else: ocgis_lh(exc=ExtentError(message=str(e)),alias=alias,logger=self._subset_log) else: sfield = field ## if empty returns are allowed, there be an empty field if sfield is not None: ## aggregate if requested if self.ops.aggregate: sfield = sfield.get_spatially_aggregated(new_spatial_uid=ugid) ## wrap the returned data. if not env.OPTIMIZE_FOR_CALC: if CFWGS84.get_is_360(sfield.spatial): if self.ops.output_format != 'nc' and self.ops.vector_wrap: ocgis_lh('wrapping output geometries',self._subset_log,alias=alias,ugid=ugid) sfield.spatial.crs.wrap(sfield.spatial) ## check for all masked values if env.OPTIMIZE_FOR_CALC is False and self.ops.file_only is False: for variable in sfield.variables.itervalues(): if variable.value.mask.all(): ## masked data may be okay depending on other opeartional ## conditions. if self.ops.snippet or self.ops.allow_empty or (self.ops.output_format == 'numpy' and self.ops.allow_empty): if self.ops.snippet: ocgis_lh('all masked data encountered but allowed for snippet', self._subset_log,alias=alias,ugid=ugid,level=logging.WARN) if self.ops.allow_empty: ocgis_lh('all masked data encountered but empty returns allowed', self._subset_log,alias=alias,ugid=ugid,level=logging.WARN) if self.ops.output_format == 'numpy': ocgis_lh('all masked data encountered but numpy data being returned allowed', logger=self._subset_log,alias=alias,ugid=ugid,level=logging.WARN) else: ## if the geometry is also masked, it is an empty spatial ## operation. if sfield.spatial.abstraction_geometry.value.mask.all(): ocgis_lh(exc=EmptyData,logger=self._subset_log) ## if none of the other conditions are met, raise the masked data error else: ocgis_lh(logger=self._subset_log,exc=MaskedDataError(),alias=alias,ugid=ugid) ## update the coordinate system of the data output if self.ops.output_crs is not None: ## if the geometry is not None, it may need to be projected to match ## the output crs. if geom is not None and crs != self.ops.output_crs: geom = project_shapely_geometry(geom,crs.sr,self.ops.output_crs.sr) sfield.spatial.update_crs(self.ops.output_crs) ## update the spatial abstraction to match the operations value. sfield ## will be none if the operation returns empty and it is allowed to have ## empty returns. if sfield is not None: sfield.spatial.abstraction = self.ops.abstraction coll.add_field(ugid,geom,alias,sfield,properties=gd.get('properties')) yield(coll)
def write(self): SEED = 1 #: random number seeding for missing data RES = 1 #: resolution of the grid ORIGIN = Point(-105, 40) #: center coordinate of upper left cell from_sr = CoordinateReferenceSystem(epsg=4326).sr to_sr = CoordinateReferenceSystem(epsg=2163).sr ORIGIN = project_shapely_geometry(ORIGIN, from_sr, to_sr) DIM = [4, 4] #: number of cells [dimx,dimy] VAR = 'foo' #: name of the data variable ## any relevant variables for the time construction TIME = {'origin': datetime.datetime(2000, 3, 1, 12, 0, 0), 'end': datetime.datetime(2000, 4, 30, 12, 0, 0), 'calendar': 'proleptic_gregorian', 'units': 'days since 2000-01-01 00:00:00', 'name': 'time'} ## any relevant variables for the spatial construction SPACE = {'row_bnds': 'bounds_latitude', 'col_bnds': 'bounds_longitude'} ## any relevant variables for the level construction LEVEL = {'name': 'level', 'n': 2} ## variables for masked data MASK = {'n': 0, 'value': float(1e20)} ## MANUAL VALUE SETTING ######################################################## d1l1 = [[1, 1, 2, 2], [1, 1, 2, 2], [3, 3, 4, 4], [3, 3, 4, 4]] VAL = np.array(d1l1).reshape(1, 1, 4, 4) VAL_MAN = None ################################################################################ ## GENERATE BASE ARRAYS ## ## make time vector delta = datetime.timedelta(1) start = TIME['origin'] timevec = [] while start <= TIME['end']: timevec.append(start) start += delta timevec = np.array(timevec) ## make vector time bounds timevec_bnds = np.empty((len(timevec), 2), dtype=object) delta = datetime.timedelta(hours=12) for idx, tv in iter_array(timevec, return_value=True): timevec_bnds[idx, 0] = tv - delta timevec_bnds[idx, 1] = tv + delta ## make the level vector levelvec = np.array([50, 150]) levelvec_bounds = np.array([[0, 100], [100, 200]]) ## make centroids col_coords = -np.arange(abs(ORIGIN.x) - RES * (DIM[0] - 1), abs(ORIGIN.x) + RES, RES) col_coords = col_coords[::-1] row_coords = np.arange(ORIGIN.y - RES * (DIM[1] - 1), ORIGIN.y + RES, RES) # row_coords = row_coords[::-1] # col,row = np.meshgrid(col_coords,row_coords) ## create bounds arrays col_bnds = self.make_bounds(col_coords, RES) row_bnds = self.make_bounds(row_coords, RES) ## make value array if VAL is not None: val = np.ones((len(timevec), LEVEL['n'], DIM[0], DIM[1]), dtype=float) * VAL else: val = VAL_MAN ## set up the mask if requested mask = np.zeros(val.shape, dtype=bool) np.random.seed(SEED) # set the random masked cells to None for ii in range(0, MASK['n']): rx = np.random.randint(0, DIM[0]) ry = np.random.randint(0, DIM[1]) mask[:, :, rx, ry] = True val = np.ma.array(val, dtype=float, mask=mask, fill_value=MASK['value']) ## WRITE THE NC FILE ########################################################### ## initialize the output file rootgrp = nc.Dataset(os.path.join(self.outdir, self.filename), 'w', format='NETCDF4') ## create the dimensions level = rootgrp.createDimension(LEVEL['name'], size=LEVEL['n']) time = rootgrp.createDimension(TIME['name'], size=len(timevec)) lat = rootgrp.createDimension('lat', size=len(row_coords)) lon = rootgrp.createDimension('lon', size=len(col_coords)) bound = rootgrp.createDimension('bound', size=2) ## create the variables times = rootgrp.createVariable(TIME['name'], 'f8', ('time',)) times.axis = 'T' bounds_times = rootgrp.createVariable('time_bnds', 'f8', ('time', 'bound')) levels = rootgrp.createVariable(LEVEL['name'], 'i4', ('level',)) levels.axis = 'Z' bounds_levels = rootgrp.createVariable('level_bnds', 'i4', ('level', 'bound')) cols = rootgrp.createVariable('longitude', 'f8', ('lon',)) cols.axis = 'X' cols.standard_name = 'projection_x_coordinate' rows = rootgrp.createVariable('latitude', 'f8', ('lat',)) rows.axis = 'Y' rows.standard_name = 'projection_y_coordinate' bounds_col = rootgrp.createVariable(SPACE['col_bnds'], 'f8', ('lon', 'bound')) bounds_row = rootgrp.createVariable(SPACE['row_bnds'], 'f8', ('lat', 'bound')) value = rootgrp.createVariable(VAR, 'f8', ('time', 'level', 'lat', 'lon'), fill_value=1e20) ## fill variables times.units = TIME['units'] times.calendar = TIME['calendar'] times[:] = nc.date2num(timevec, units=times.units, calendar=times.calendar) bounds_times[:] = nc.date2num(timevec_bnds, units=times.units, calendar=times.calendar) levels[:] = levelvec bounds_levels[:] = levelvec_bounds cols[:] = col_coords rows[:] = row_coords bounds_col[:, :] = col_bnds bounds_row[:, :] = row_bnds value[:, :, :, :] = val value.missing_value = MASK['value'] value.standard_name = 'foo' value.long_name = 'foo_foo' value.units = 'huge' value.grid_mapping = 'crs' grid_mapping = rootgrp.createVariable('crs', 'c') grid_mapping.grid_mapping_name = "lambert_conformal_conic" grid_mapping.standard_parallel = [30., 60.] grid_mapping.longitude_of_central_meridian = -97. grid_mapping.latitude_of_projection_origin = 47.5 grid_mapping.false_easting = 3325000. grid_mapping.false_northing = 2700000. # add bounds attributes times.bounds = bounds_times._name rows.bounds = bounds_row._name cols.bounds = bounds_col._name levels.bounds = bounds_levels._name rootgrp.close()