def get_multi_line_string(self, to_wkt=None, tolerance=0): '''\ Function to return a shapely MultiLineString object representing the line dataset ''' _line_indices, line_start_indices = np.sort( np.unique(self.line_index, return_index=True)) line_end_indices = np.array(line_start_indices) line_end_indices[0:-1] = line_start_indices[1:] line_end_indices[-1] = self.point_count line_end_indices = line_end_indices - 1 line_list = [] for line_index in range(len(line_start_indices)): line_slice = slice(line_start_indices[line_index], line_end_indices[line_index] + 1) line_vertices = self.xycoords[line_slice] line_vertices = line_vertices[~np.any( np.isnan(line_vertices), axis=1)] # Discard null coordinates if len( line_vertices ) >= 2: # LineStrings must have at least 2 coordinate tuples line_list.append( LineString( transform_coords(line_vertices, self.wkt, to_wkt)).simplify(tolerance)) return MultiLineString(line_list)
def get_convex_hull(self, to_wkt=None): ''' Function to return vertex coordinates of a convex hull polygon around all points ''' convex_hull = points2convex_hull(self.xycoords) return transform_coords(convex_hull, self.wkt, to_wkt)
def get_reprojected_bounds(self, bounds, from_wkt, to_wkt): ''' Function to take a bounding box specified in one CRS and return its smallest containing bounding box in a new CRS @parameter bounds: bounding box specified as tuple(xmin, ymin, xmax, ymax) in CRS from_wkt @parameter from_wkt: WKT for CRS from which to transform bounds @parameter to_wkt: WKT for CRS to which to transform bounds @return reprojected_bounding_box: bounding box specified as tuple(xmin, ymin, xmax, ymax) in CRS to_wkt ''' if (to_wkt is None) or (from_wkt is None) or (to_wkt == from_wkt): return bounds # Need to look at all four bounding box corners, not just LL & UR original_bounding_box = ((bounds[0], bounds[1]), (bounds[2], bounds[1]), (bounds[2], bounds[3]), (bounds[0], bounds[3])) reprojected_bounding_box = np.array( transform_coords(original_bounding_box, from_wkt, to_wkt)) return [ min(reprojected_bounding_box[:, 0]), min(reprojected_bounding_box[:, 1]), max(reprojected_bounding_box[:, 0]), max(reprojected_bounding_box[:, 1]) ]
def test_transform_coords(self): print('Testing transform_coords function with single coordinate{}'. format(TestCRSUtils.EPSG4326_COORDS)) utm_coords = transform_coords(TestCRSUtils.EPSG4326_COORDS, TestCRSUtils.EPSG4326_WKT, TestCRSUtils.UTM_WKT) assert (utm_coords == np.array(TestCRSUtils.UTM_COORDS) ).all(), 'Incorrect UTM coordinates: {} instead of {}'.format( utm_coords, TestCRSUtils.UTM_COORDS) print('Testing transform_coords function with multi coordinate {}'. format(TestCRSUtils.EPSG4326_COORD_ARRAY)) utm_coord_array = transform_coords(TestCRSUtils.EPSG4326_COORD_ARRAY, TestCRSUtils.EPSG4326_WKT, TestCRSUtils.UTM_WKT) assert (utm_coord_array == np.array(TestCRSUtils.UTM_COORD_ARRAY) ).all(), 'Incorrect UTM coordinates: {} instead of {}'.format( utm_coord_array, TestCRSUtils.UTM_COORD_ARRAY)
def get_convex_hull(self, to_wkt=None): '''\ Function to return n x 2 array of coordinates for convex hull based on line start/end points Implements abstract base function in NetCDFUtils @param to_wkt: CRS WKT for shape ''' points = transform_coords(self.get_line_sample_points(), self.wkt, to_wkt) try: convex_hull = points2convex_hull(points) except: #logger.info('Unable to compute convex hull. Using rectangular bounding box instead.') convex_hull = self.native_bbox return convex_hull
def get_spatial_mask(self, bounds, bounds_wkt=None): ''' Return boolean mask of dimension 'point' for all coordinates within specified bounds and CRS ''' coordinates = self.xycoords if bounds_wkt is not None: coordinates = np.array( transform_coords(self.xycoords, self.wkt, bounds_wkt)) bounds_half_size = abs( np.array([bounds[2] - bounds[0], bounds[3] - bounds[1]])) / 2.0 bounds_centroid = np.array([bounds[0], bounds[1]]) + bounds_half_size # Return true for each point which is <= bounds_half_size distance from bounds_centroid return np.all(ne.evaluate( "abs(coordinates - bounds_centroid) <= bounds_half_size"), axis=1)
def get_gdal_grid_values(gdal_dataset, sample_points, from_crs, band_no=1): ''' Function to return values at a series of points from a GDAL dataset ''' geotransform = gdal_dataset.GetGeoTransform() to_crs = gdal_dataset.GetProjection() gdal_band = gdal_dataset.GetRasterBand(band_no) native_sample_points = transform_coords(sample_points, from_crs, to_crs) #TODO: Make this faster values = [] for point in native_sample_points: indices = (int((point[0] - geotransform[0]) / geotransform[1] + 0.5), int((point[1] - geotransform[3]) / geotransform[5] + 0.5)) value = gdal_band.ReadAsArray(xoff=indices[0], yoff=indices[1], win_xsize=1, win_ysize=1)[0,0] #print point, indices, value values.append(value) return np.array(values)
def grid_points_gdal(cond_point_utils_inst, grid_resolution, variables=None, native_grid_bounds=None, reprojected_grid_bounds=None, grid_wkt=None, point_step=1, grid_kwargs=None, depth_inds=None): ''' Function that grids points in the cond_point_utils instance within a bounding box using gdal_grid. @parameter cond_point_utils_inst: instance of cond_point_utils from geophys_utils @parameter grid_resolution: cell size of regular grid in grid CRS units @parameter variables: Single variable name string or list of multiple variable name strings. Defaults to all point variables @parameter native_grid_bounds: Spatial bounding box of area to grid in native coordinates @parameter reprojected_grid_bounds: Spatial bounding box of area to grid in grid coordinates @parameter grid_wkt: WKT for grid coordinate reference system. Defaults to native CRS @parameter point_step: Sampling spacing for points. 1 (default) means every point, 2 means every second point, etc. @parameter depth inds: list of depth indices to grid. @return grid: dictionary with gridded data, geotransform and wkt ''' assert not ( native_grid_bounds and reprojected_grid_bounds ), 'Either native_grid_bounds or reprojected_grid_bounds can be provided, but not both' # Grid all data variables if not specified variables = variables or cond_point_utils_inst.point_variables # Allow single variable to be given as a string single_var = (type(variables) == str) if single_var: variables = [variables] if native_grid_bounds: reprojected_grid_bounds = cond_point_utils_inst.get_reprojected_bounds( native_grid_bounds, cond_point_utils_inst.wkt, grid_wkt) elif reprojected_grid_bounds: native_grid_bounds = cond_point_utils_inst.get_reprojected_bounds( reprojected_grid_bounds, grid_wkt, cond_point_utils_inst.wkt) else: # No reprojection required native_grid_bounds = cond_point_utils_inst.bounds reprojected_grid_bounds = cond_point_utils_inst.bounds # Determine spatial grid bounds rounded out to nearest GRID_RESOLUTION multiple pixel_centre_bounds = ( round( math.floor(reprojected_grid_bounds[0] / grid_resolution) * grid_resolution, 6), round( math.floor(reprojected_grid_bounds[1] / grid_resolution) * grid_resolution, 6), round( math.floor(reprojected_grid_bounds[2] / grid_resolution - 1.0) * grid_resolution + grid_resolution, 6), round( math.floor(reprojected_grid_bounds[3] / grid_resolution - 1.0) * grid_resolution + grid_resolution, 6)) # Extend area for points an arbitrary two cells out beyond grid extents for nice interpolation at edges expanded_grid_bounds = [ pixel_centre_bounds[0] - 2 * grid_resolution, pixel_centre_bounds[1] - 2 * grid_resolution, pixel_centre_bounds[2] + 2 * grid_resolution, pixel_centre_bounds[3] + 2 * grid_resolution ] expanded_grid_size = [ expanded_grid_bounds[dim_index + 2] - expanded_grid_bounds[dim_index] for dim_index in range(2) ] # Get width and height of grid width = int(expanded_grid_size[0] / grid_resolution) height = int(expanded_grid_size[1] / grid_resolution) spatial_subset_mask = cond_point_utils_inst.get_spatial_mask( cond_point_utils_inst.get_reprojected_bounds( expanded_grid_bounds, grid_wkt, cond_point_utils_inst.wkt)) # Skip points to reduce memory requirements # TODO: Implement function which grids spatial subsets. point_subset_mask = np.zeros(shape=( cond_point_utils_inst.netcdf_dataset.dimensions['point'].size, ), dtype=bool) point_subset_mask[0:-1:point_step] = True point_subset_mask = np.logical_and(spatial_subset_mask, point_subset_mask) coordinates = cond_point_utils_inst.xycoords[point_subset_mask] # Reproject coordinates if required if grid_wkt is not None: # N.B: Be careful about XY vs YX coordinate order coordinates = np.array( transform_coords(coordinates[:], cond_point_utils_inst.wkt, grid_wkt)) grid = {} for variable in [ cond_point_utils_inst.netcdf_dataset.variables[var_name] for var_name in variables ]: # If preferences are not given we grid using defaults if grid_kwargs is None: grid_kwargs = {} grid_kwargs[variable.name] = {} if not 'log_grid' in grid_kwargs: grid_kwargs[variable.name]['log_grid'] = False # Check for the algorithm in the grid kwargs if not 'gdal_algorithm' in grid_kwargs: s = 'invdist:power=2:radius1=250:' s += 'radius2=250:max_points=15:' s += 'min_points=2:nodata=-32768.0' # Defaut grid_kwargs[variable.name]['gdal_algorithm'] = s if not 'format' in grid_kwargs: # Defaut grid_kwargs[variable.name]['format'] = 'GTiff' if not 'outputType' in grid_kwargs: # Defaut grid_kwargs[variable.name]['outputType'] = gdal.GDT_Float32 grid_kwargs[variable.name]['width'] = width grid_kwargs[variable.name]['height'] = height grid_kwargs[variable.name]['outputSRS'] = grid_wkt grid_kwargs[variable.name]['outputBounds'] = expanded_grid_bounds # For 2d arrays if len(variable.shape) == 2: if depth_inds is not None: nlayers = len(depth_inds) else: nlayers = variable.shape[1] depth_inds = np.arange(0, nlayers) a = np.nan * np.ones(shape=(nlayers, int(height), int(width)), dtype=np.float32) # Iterate through the layers for i, ind in enumerate(depth_inds): var_array = variable[point_subset_mask, ind].reshape([-1, 1]) a[i], geotransform = grid_var(var_array, coordinates, grid_kwargs[variable.name]) elif len(variable.shape) == 1: # Create a temporary array var_array = variable[point_subset_mask].reshape([-1, 1]) a, geotransform = grid_var(var_array, coordinates, grid_kwargs[variable.name]) grid[variable.name] = a grid['geotransform'] = geotransform grid['wkt'] = grid_wkt return grid
def nearest_neighbours(self, coordinates, wkt=None, points_required=1, max_distance=None, secondary_mask=None): ''' Function to determine nearest neighbours using cKDTree N.B: All distances are expressed in the native dataset CRS @param coordinates: two-element XY coordinate tuple, list or array @param wkt: Well-known text of coordinate CRS - defaults to native dataset CRS @param points_required: Number of points to retrieve. Default=1 @param max_distance: Maximum distance to search from target coordinate - STRONGLY ADVISED TO SPECIFY SENSIBLE VALUE OF max_distance TO LIMIT SEARCH AREA @param secondary_mask: Boolean array of same shape as point array used to filter points. None = no filter. @return distances: distances from the target coordinate for each of the points_required nearest points @return indices: point indices for each of the points_required nearest points ''' if wkt: reprojected_coords = transform_coords(coordinates, wkt, self.wkt) else: reprojected_coords = coordinates if secondary_mask is None: secondary_mask = np.ones(shape=(self.point_count, ), dtype=bool) else: assert secondary_mask.shape == (self.point_count, ) if max_distance: # max_distance has been specified logger.debug('Computing spatial subset mask...') spatial_mask = self.get_spatial_mask([ reprojected_coords[0] - max_distance, reprojected_coords[1] - max_distance, reprojected_coords[0] + max_distance, reprojected_coords[1] + max_distance ]) point_indices = np.where( np.logical_and(spatial_mask, secondary_mask))[0] if not len(point_indices): logger.debug('No points within distance {} of {}'.format( max_distance, reprojected_coords)) return [], [] # Set up KDTree for nearest neighbour queries logger.debug( 'Indexing spatial subset with {} points into KDTree...'.format( np.count_nonzero(spatial_mask))) kdtree = cKDTree(data=self.xycoords[point_indices]) logger.debug('Finished indexing spatial subset into KDTree.') else: # Consider ALL points max_distance = np.inf kdtree = self.kdtree distances, indices = kdtree.query(x=np.array(reprojected_coords), k=points_required, distance_upper_bound=max_distance) if max_distance == np.inf: return distances, indices else: # Return indices of complete coordinate array, not the spatial subset return distances, np.where(spatial_mask)[0][indices]
def grid_points(self, grid_resolution, variables=None, native_grid_bounds=None, reprojected_grid_bounds=None, resampling_method='linear', grid_wkt=None, point_step=1): ''' Function to grid points in a specified bounding rectangle to a regular grid of the specified resolution and crs @parameter grid_resolution: cell size of regular grid in grid CRS units @parameter variables: Single variable name string or list of multiple variable name strings. Defaults to all point variables @parameter native_grid_bounds: Spatial bounding box of area to grid in native coordinates @parameter reprojected_grid_bounds: Spatial bounding box of area to grid in grid coordinates @parameter resampling_method: Resampling method for gridding. 'linear' (default), 'nearest' or 'cubic'. See https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.griddata.html @parameter grid_wkt: WKT for grid coordinate reference system. Defaults to native CRS @parameter point_step: Sampling spacing for points. 1 (default) means every point, 2 means every second point, etc. @return grids: dict of grid arrays keyed by variable name if parameter 'variables' value was a list, or a single grid array if 'variable' parameter value was a string @return wkt: WKT for grid coordinate reference system. @return geotransform: GDAL GeoTransform for grid ''' assert not ( native_grid_bounds and reprojected_grid_bounds ), 'Either native_grid_bounds or reprojected_grid_bounds can be provided, but not both' # Grid all data variables if not specified variables = variables or self.point_variables # Allow single variable to be given as a string single_var = (type(variables) == str) if single_var: variables = [variables] if native_grid_bounds: reprojected_grid_bounds = self.get_reprojected_bounds( native_grid_bounds, self.wkt, grid_wkt) elif reprojected_grid_bounds: native_grid_bounds = self.get_reprojected_bounds( reprojected_grid_bounds, grid_wkt, self.wkt) else: # No reprojection required native_grid_bounds = self.bounds reprojected_grid_bounds = self.bounds # Determine spatial grid bounds rounded out to nearest GRID_RESOLUTION multiple pixel_centre_bounds = ( round( math.floor(reprojected_grid_bounds[0] / grid_resolution) * grid_resolution, 6), round( math.floor(reprojected_grid_bounds[1] / grid_resolution) * grid_resolution, 6), round( math.floor(reprojected_grid_bounds[2] / grid_resolution - 1.0) * grid_resolution + grid_resolution, 6), round( math.floor(reprojected_grid_bounds[3] / grid_resolution - 1.0) * grid_resolution + grid_resolution, 6)) grid_size = [ pixel_centre_bounds[dim_index + 2] - pixel_centre_bounds[dim_index] for dim_index in range(2) ] # Extend area for points an arbitrary 4% out beyond grid extents for nice interpolation at edges expanded_grid_bounds = [ pixel_centre_bounds[0] - grid_size[0] / 50.0, pixel_centre_bounds[1] - grid_size[0] / 50.0, pixel_centre_bounds[2] + grid_size[1] / 50.0, pixel_centre_bounds[3] + grid_size[1] / 50.0 ] spatial_subset_mask = self.get_spatial_mask( self.get_reprojected_bounds(expanded_grid_bounds, grid_wkt, self.wkt)) # Create grids of Y and X values. Note YX ordering and inverted Y # Note GRID_RESOLUTION/2.0 fudge to avoid truncation due to rounding error grid_y, grid_x = np.mgrid[ pixel_centre_bounds[3]:pixel_centre_bounds[1] - grid_resolution / 2.0:-grid_resolution, pixel_centre_bounds[0]:pixel_centre_bounds[2] + grid_resolution / 2.0:grid_resolution] # Skip points to reduce memory requirements #TODO: Implement function which grids spatial subsets. point_subset_mask = np.zeros( shape=(self.netcdf_dataset.dimensions['point'].size, ), dtype=bool) point_subset_mask[0:-1:point_step] = True point_subset_mask = np.logical_and(spatial_subset_mask, point_subset_mask) coordinates = self.xycoords[point_subset_mask] # Reproject coordinates if required if grid_wkt is not None: # N.B: Be careful about XY vs YX coordinate order coordinates = np.array( transform_coords(coordinates[:], self.wkt, grid_wkt)) # Interpolate required values to the grid - Note YX ordering for image grids = {} for variable in [ self.netcdf_dataset.variables[var_name] for var_name in variables ]: grids[variable.name] = griddata( coordinates[:, ::-1], variable[:] [point_subset_mask], #TODO: Check why this is faster than direct indexing (grid_y, grid_x), method=resampling_method) if single_var: grids = list(grids.values())[0] # crs:GeoTransform = "109.1002342895272 0.00833333 0 -9.354948067227777 0 -0.00833333 " geotransform = [ pixel_centre_bounds[0] - grid_resolution / 2.0, grid_resolution, 0, pixel_centre_bounds[3] + grid_resolution / 2.0, 0, -grid_resolution ] return grids, (grid_wkt or self.wkt), geotransform
def main(): ''' Main function ''' def get_xml_text(xml_template_path, metadata_object): '''Helper function to perform substitutions on XML template text ''' template_dir = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'templates') jinja_environment = Environment( loader=FileSystemLoader(template_dir or './'), autoescape=select_autoescape(['html', 'xml'])) xml_template = jinja_environment.get_template(xml_template_path, parent=None) value_dict = dict( metadata_object.metadata_dict['Template']) # Copy template values # Convert multiple sets of comma-separated lists to lists of strings to a list of dicts #TODO: Make this slicker value_dict['keywords'] = [] for keyword_list_key in [ key for key in value_dict.keys() if re.match('^KEYWORD_\w+_LIST$', key) ]: keywords = [ keyword.strip() for keyword in value_dict[keyword_list_key].split(',') ] keyword_code = value_dict[re.sub('_LIST$', '_CODE', keyword_list_key)] value_dict['keywords'] += [{ 'value': keyword, 'code': keyword_code } for keyword in keywords] # Create dict containing distribution info for DOI if required value_dict['distributions'] = [] dataset_doi = metadata_object.get_metadata(['Calculated', 'DOI']) if dataset_doi: try: distribution_dict = { 'formatSpecification': 'html', 'distributor_name': metadata_object.get_metadata( ['Template', 'ORGANISATION_NAME']), 'distributor_telephone': metadata_object.get_metadata( ['Template', 'ORGANISATION_PHONE']), 'distributor_address': metadata_object.get_metadata( ['Template', 'ORGANISATION_ADDRESS']), 'distributor_city': metadata_object.get_metadata( ['Template', 'ORGANISATION_CITY']), 'distributor_state': metadata_object.get_metadata( ['Template', 'ORGANISATION_STATE']), 'distributor_postcode': metadata_object.get_metadata( ['Template', 'ORGANISATION_POSTCODE']), 'distributor_country': metadata_object.get_metadata( ['Template', 'ORGANISATION_COUNTRY']), 'distributor_email': metadata_object.get_metadata( ['Template', 'ORGANISATION_EMAIL']), 'url': dataset_doi, 'protocol': 'WWW:LINK-1.0-http--link', 'name': 'Digital Object Identifier for dataset %s' % metadata_object.get_metadata(['Calculated', 'UUID']), 'description': 'Dataset DOI' } for key, value in distribution_dict.iteritems(): assert value, '%s has no value defined' % key value_dict['distributions'].append(distribution_dict) except Exception as e: print 'WARNING: Unable to create DOI distribution: %s' % e.message return xml_template.render(**value_dict) def str2datelist(multi_date_string): ''' Helper function to convert comma-separated string containing dates to a list of dates ''' date_list = [] for datetime_string in multi_date_string.split(','): for format in ['%d-%b-%y', '%Y-%m-%dT%H:%M:%S']: try: date_list.append( datetime.strptime(datetime_string.strip(), format).date()) except: continue return date_list # Start of main function assert len(sys.argv) >= 4 and len( sys.argv ) <= 8, 'Usage: %s <json_text_template_path> <xml_template_path> <netcdf_path> [<xml_output_dir>]' % sys.argv[ 0] json_text_template_path = sys.argv[1] xml_template_path = sys.argv[2] netcdf_path = sys.argv[3] if len(sys.argv) >= 5: xml_dir = sys.argv[4] else: xml_dir = '.' # Optional arguments for DB connection - not required at NCI if len(sys.argv) == 8: db_user = sys.argv[5] db_password = sys.argv[6] db_alias = sys.argv[7] else: db_user = None db_password = None db_alias = None xml_path = os.path.abspath( os.path.join( xml_dir, os.path.splitext(os.path.basename(netcdf_path))[0] + '.xml')) print xml_dir, xml_path metadata_object = Metadata() netcdf_metadata = NetCDFMetadata(netcdf_path) metadata_object.merge_root_metadata_from_object(netcdf_metadata) nc_dataset = netCDF4.Dataset( netcdf_path, 'r+') # Allow for updating of netCDF attributes like uuid # JetCat and Survey metadata can either take a list of survey IDs as source(s) or a filename from which to parse them try: survey_ids = nc_dataset.survey_id print 'Survey ID "%s" found in netCDF attributes' % survey_ids source = [ int(value_string.strip()) for value_string in survey_ids.split(',') if value_string.strip() ] except: source = netcdf_path # jetcat_metadata = JetCatMetadata(source, jetcat_path=jetcat_path) # metadata_object.merge_root_metadata_from_object(jetcat_metadata) try: survey_metadata = SurveyMetadata(source) metadata_object.merge_root_metadata_from_object(survey_metadata) except Exception as e: print 'Unable to read from Survey API:\n%s\nAttempting direct Oracle DB read' % e.message try: survey_metadata = ArgusMetadata( db_user, db_password, db_alias, source ) # This will fail if we haven't been able to import ArgusMetadata metadata_object.merge_root_metadata( 'Survey', survey_metadata.metadata_dict, overwrite=True) # Fake Survey metadata from DB query except Exception as e: print 'Unable to perform direct Oracle DB read: %s' % e.message nc_grid_utils = NetCDFGridUtils(nc_dataset) # Add some calculated values to the metadata calculated_values = {} metadata_object.metadata_dict['Calculated'] = calculated_values calculated_values['FILENAME'] = os.path.basename(netcdf_path) #calculated_values['CELLSIZE'] = str((nc_grid_utils.pixel_size[0] + nc_grid_utils.pixel_size[1]) / 2.0) calculated_values['CELLSIZE_M'] = str( int( round((nc_grid_utils.nominal_pixel_metres[0] + nc_grid_utils.nominal_pixel_metres[1]) / 20.0) * 10)) calculated_values['CELLSIZE_DEG'] = str( round((nc_grid_utils.nominal_pixel_degrees[0] + nc_grid_utils.nominal_pixel_degrees[1]) / 2.0, 8)) try: calculated_values['START_DATE'] = min( str2datelist( str(metadata_object.get_metadata(['Survey', 'STARTDATE'])))).isoformat() except ValueError: calculated_values['START_DATE'] = None try: calculated_values['END_DATE'] = max( str2datelist( str(metadata_object.get_metadata(['Survey', 'ENDDATE'])))).isoformat() except ValueError: calculated_values['END_DATE'] = None # Find survey year from end date isoformat string try: calculated_values['YEAR'] = re.match( '^(\d{4})-', calculated_values['END_DATE']).group(1) except: calculated_values['YEAR'] = 'UNKNOWN' #history = "Wed Oct 26 14:34:42 2016: GDAL CreateCopy( /local/el8/axi547/tmp/mWA0769_770_772_773.nc, ... )" #date_modified = "2016-08-29T10:51:42" try: try: conversion_datetime_string = re.match( '^(.+):.*', str(metadata_object.get_metadata(['NetCDF', 'history']))).group(1) conversion_datetime_string = datetime.strptime( conversion_datetime_string, '%a %b %d %H:%M:%S %Y').isoformat() except: conversion_datetime_string = metadata_object.get_metadata( ['NetCDF', 'date_modified']) or 'UNKNOWN' except: conversion_datetime_string = 'UNKNOWN' calculated_values['CONVERSION_DATETIME'] = conversion_datetime_string survey_id = str(metadata_object.get_metadata(['Survey', 'SURVEYID'])) try: dataset_survey_id = str(nc_dataset.survey_id) assert (set([ int(value_string.strip()) for value_string in dataset_survey_id.split(',') if value_string.strip() ]) == set([ int(value_string.strip()) for value_string in survey_id.split(',') if value_string.strip() ])), 'NetCDF survey ID %s is inconsistent with %s' % ( dataset_survey_id, survey_id) except: nc_dataset.survey_id = str(survey_id) nc_dataset.sync() print 'Survey ID %s written to netCDF file' % survey_id dataset_uuid = metadata_object.get_metadata(['NetCDF', 'uuid']) if not dataset_uuid: # Create a new UUID and write it to the netCDF file dataset_uuid = uuid.uuid4() nc_dataset.uuid = dataset_uuid nc_dataset.sync() print 'Fresh UUID %s generated and written to netCDF file' % dataset_uuid calculated_values['UUID'] = str(dataset_uuid) dataset_doi = metadata_object.get_metadata(['NetCDF', 'doi']) if not dataset_doi and False: #TODO: Mint a new DOI and write it to the netCDF file dataset_doi = '' #TODO: Replace this with call to DOI minter - might be problematic from a non-GA source address nc_dataset.doi = dataset_doi nc_dataset.sync() print 'Fresh DOI %s generated and written to netCDF file' % dataset_uuid if dataset_doi: calculated_values['DOI'] = str(dataset_doi) WGS84_bbox = transform_coords(nc_grid_utils.native_bbox, nc_grid_utils.crs, 'EPSG:4326') WGS84_extents = [ min([coordinate[0] for coordinate in WGS84_bbox]), min([coordinate[1] for coordinate in WGS84_bbox]), max([coordinate[0] for coordinate in WGS84_bbox]), max([coordinate[1] for coordinate in WGS84_bbox]) ] calculated_values['ELON'] = str(WGS84_extents[0]) calculated_values['SLAT'] = str(WGS84_extents[1]) calculated_values['WLON'] = str(WGS84_extents[2]) calculated_values['NLAT'] = str(WGS84_extents[3]) #template_class = None template_metadata_object = TemplateMetadata(json_text_template_path, metadata_object) metadata_object.merge_root_metadata_from_object(template_metadata_object) #pprint(metadata_object.metadata_dict) xml_text = get_xml_text(xml_template_path, metadata_object) #print xml_text xml_file = open(xml_path, 'w') xml_file.write(xml_text) xml_file.close() print 'XML written to %s' % xml_path