from netCDF4 import Dataset
import numpy as np
import math
degreesToRadians = math.pi / 180.0
radiansToDegrees = 180.0 / math.pi
[docs]def write_scrip_file(scripFilename, title, nCells, maxEdges,
                     latCell, lonCell, corner_lat, corner_lon, mask=None):
    """
    A low-level function for writing a SCRIP file for the given MPAS-Seaice
    mesh
    Parameters
    ----------
    scripFilename : str
        The name of the resulting SCRIP file
    title : str
        A string to include as the ``title`` attribute in the SCRIP file
    nCells : int
        The number of cells in the mesh
    maxEdges : int
        The maximum number of edges/vertices on a cell in the mesh
    latCell : numpy.ndarray
        The latitude (in radians) of cell centers
    lonCell : numpy.ndarray
        The longitude (in radians) of cell centers
    corner_lat : numpy.ndarray
        The latitude (in radians) of cell vertices
    corner_lon : numpy.ndarray
        The longitude (in radians) of cell vertices
    mask  : numpy.ndarray, optional
        A mask of where cells are valid
    """
    # output mesh file
    scripFile = Dataset(scripFilename, "w", format="NETCDF3_CLASSIC")
    scripFile.title = title.strip()
    scripFile.createDimension("grid_size", nCells)
    scripFile.createDimension("grid_corners", maxEdges)
    scripFile.createDimension("grid_rank", 1)
    grid_dims = scripFile.createVariable(
        "grid_dims", "i", dimensions=["grid_rank"])
    grid_center_lat = scripFile.createVariable(
        "grid_center_lat", "d", dimensions=["grid_size"])
    grid_center_lon = scripFile.createVariable(
        "grid_center_lon", "d", dimensions=["grid_size"])
    grid_imask = scripFile.createVariable(
        "grid_imask", "i", dimensions=["grid_size"])
    grid_corner_lat = scripFile.createVariable(
        "grid_corner_lat", "d", dimensions=[
            "grid_size", "grid_corners"])
    grid_corner_lon = scripFile.createVariable(
        "grid_corner_lon", "d", dimensions=[
            "grid_size", "grid_corners"])
    grid_center_lat.units = "radians"
    grid_center_lon.units = "radians"
    grid_imask.units = "unitless"
    grid_corner_lat.units = "radians"
    grid_corner_lon.units = "radians"
    grid_dims[:] = [nCells]
    grid_center_lat[:] = latCell
    grid_center_lon[:] = lonCell
    if mask is None:
        grid_imask[:] = np.ones(nCells, dtype="i")
    else:
        grid_imask[:] = mask[:]
    grid_corner_lat[:, :] = corner_lat[:, :]
    grid_corner_lon[:, :] = corner_lon[:, :]
    scripFile.close() 
# ------------------------------------------------------------------------------------
[docs]def write_2D_scripfile(filenameScripOut, scripTitle, nColumns, nRows,
                       latsCentre, lonsCentre, latsVertex, lonsVertex,
                       degrees=False):
    """
    Write a SCRIP file for the given 2D grid
    Parameters
    ----------
    filenameScripOut : str
        The name of the resulting SCRIP file
    scripTitle : str
        A string to include as the ``title`` attribute in the SCRIP file
    nColumns : int
        The number of columns in the grid
    nRows : int
        The number of rows in the grid
    latsCentre : numpy.ndarray
        The latitude (in radians) of cell centers
    lonsCentre : numpy.ndarray
        The longitude (in radians) of cell centers
    latsVertex : numpy.ndarray
        The latitude (in radians) of cell vertices
    lonsVertex : numpy.ndarray
        The longitude (in radians) of cell vertices
    degrees  : bool, optional
        Whether the latitude and longitude variables are in degrees (as opposed
        to radians)
    """
    if degrees:
        scaling = np.pi / 180.
    else:
        scaling = 1.0
    fileOut = Dataset(filenameScripOut, "w", format="NETCDF3_CLASSIC")
    fileOut.title = scripTitle
    grid_size = nRows * nColumns
    grid_corners = 4
    grid_rank = 2
    fileOut.createDimension("grid_size", grid_size)
    fileOut.createDimension("grid_corners", grid_corners)
    fileOut.createDimension("grid_rank", grid_rank)
    grid_dimsVar = fileOut.createVariable(
        "grid_dims", "i", dimensions=("grid_rank",))
    grid_center_latVar = fileOut.createVariable(
        "grid_center_lat", "d", dimensions=("grid_size",))
    grid_center_lonVar = fileOut.createVariable(
        "grid_center_lon", "d", dimensions=("grid_size",))
    grid_imaskVar = fileOut.createVariable(
        "grid_imask", "i", dimensions=("grid_size",))
    grid_corner_latVar = fileOut.createVariable(
        "grid_corner_lat", "d", dimensions=(
            "grid_size", "grid_corners"))
    grid_corner_lonVar = fileOut.createVariable(
        "grid_corner_lon", "d", dimensions=(
            "grid_size", "grid_corners"))
    grid_dims = np.zeros(grid_rank, dtype="i")
    grid_center_lat = np.zeros(grid_size, dtype="d")
    grid_center_lon = np.zeros(grid_size, dtype="d")
    grid_imask = np.zeros(grid_size, dtype="i")
    grid_corner_lat = np.zeros((grid_size, grid_corners), dtype="d")
    grid_corner_lon = np.zeros((grid_size, grid_corners), dtype="d")
    grid_dims[0] = nColumns
    grid_dims[1] = nRows
    for iRow in range(0, nRows):
        for iColumn in range(0, nColumns):
            i = iColumn + iRow * nColumns
            grid_center_lat[i] = latsCentre[iRow, iColumn] * scaling
            grid_center_lon[i] = lonsCentre[iRow, iColumn] * scaling
            grid_imask[i] = 1
            for iVertex in range(0, 4):
                grid_corner_lat[i, iVertex] = \
                    
latsVertex[iRow, iColumn, iVertex] * scaling
                grid_corner_lon[i, iVertex] = \
                    
lonsVertex[iRow, iColumn, iVertex] * scaling
    grid_dimsVar[:] = grid_dims
    grid_center_latVar[:] = grid_center_lat
    grid_center_lonVar[:] = grid_center_lon
    grid_imaskVar[:] = grid_imask
    grid_corner_latVar[:] = grid_corner_lat
    grid_corner_lonVar[:] = grid_corner_lon
    grid_center_latVar.units = "radians"
    grid_center_lonVar.units = "radians"
    grid_imaskVar.units = "unitless"
    grid_corner_latVar.units = "radians"
    grid_corner_lonVar.units = "radians"
    fileOut.close() 
# ---------------------------------------------------------------------
[docs]def make_mpas_scripfile_on_cells(meshFilename, scripFilename, title):
    """
    Write a SCRIP file for cel quantities on the given MPAS-Seaice mesh
    Parameters
    ----------
    meshFilename : str
        The name of a file containing the MPAS-Seaice mesh
    scripFilename : str
        The name of the resulting SCRIP file
    title : str
        A string to include as the ``title`` attribute in the SCRIP file
    """
    # input mesh data
    meshFile = Dataset(meshFilename, "r")
    nCells = len(meshFile.dimensions["nCells"])
    maxEdges = len(meshFile.dimensions["maxEdges"])
    latCell = meshFile.variables["latCell"][:]
    lonCell = meshFile.variables["lonCell"][:]
    latVertex = meshFile.variables["latVertex"][:]
    lonVertex = meshFile.variables["lonVertex"][:]
    nEdgesOnCell = meshFile.variables["nEdgesOnCell"][:]
    verticesOnCell = meshFile.variables["verticesOnCell"][:]
    meshFile.close()
    # make corner arrays
    corner_lat = np.zeros((nCells, maxEdges), dtype="d")
    corner_lon = np.zeros((nCells, maxEdges), dtype="d")
    for iCell in range(0, nCells):
        for iVertexOnCell in range(0, nEdgesOnCell[iCell]):
            iVertex = verticesOnCell[iCell, iVertexOnCell] - 1
            corner_lat[iCell, iVertexOnCell] = latVertex[iVertex]
            corner_lon[iCell, iVertexOnCell] = lonVertex[iVertex]
        for iVertexOnCell in range(nEdgesOnCell[iCell], maxEdges):
            corner_lat[iCell,
                       iVertexOnCell] = corner_lat[iCell,
                                                   nEdgesOnCell[iCell] - 1]
            corner_lon[iCell,
                       iVertexOnCell] = corner_lon[iCell,
                                                   nEdgesOnCell[iCell] - 1]
    # create the scrip file
    write_scrip_file(
        scripFilename,
        title,
        nCells,
        maxEdges,
        latCell,
        lonCell,
        corner_lat,
        corner_lon) 
# ---------------------------------------------------------------------
[docs]def make_mpas_scripfile_on_vertices(meshFilename, scripFilename, title):
    """
    Write a SCRIP file for vertex quantities on the given MPAS-Seaice mesh
    Parameters
    ----------
    meshFilename : str
        The name of a file containing the MPAS-Seaice mesh
    scripFilename : str
        The name of the resulting SCRIP file
    title : str
        A string to include as the ``title`` attribute in the SCRIP file
    """
    # input mesh data
    meshFile = Dataset(meshFilename, "r")
    nVertices = len(meshFile.dimensions["nVertices"])
    vertexDegree = len(meshFile.dimensions["vertexDegree"])
    latCell = meshFile.variables["latCell"][:]
    lonCell = meshFile.variables["lonCell"][:]
    latVertex = meshFile.variables["latVertex"][:]
    lonVertex = meshFile.variables["lonVertex"][:]
    cellsOnVertex = meshFile.variables["cellsOnVertex"][:]
    meshFile.close()
    # make corner arrays
    corner_lat = np.zeros((nVertices, vertexDegree), dtype="d")
    corner_lon = np.zeros((nVertices, vertexDegree), dtype="d")
    for iVertex in range(0, nVertices):
        for iCellOnVertex in range(0, vertexDegree):
            iCell = cellsOnVertex[iVertex, iCellOnVertex] - 1
            if iCell != -1:
                corner_lat[iVertex, iCellOnVertex] = latCell[iCell]
                corner_lon[iVertex, iCellOnVertex] = lonCell[iCell]
            else:
                corner_lat[iVertex, iCellOnVertex], \
                    
corner_lon[iVertex, iCellOnVertex] = \
                    
_estimate_missing_cell_latlon(
                        iVertex, iCellOnVertex, vertexDegree, cellsOnVertex,
                        latCell, lonCell, latVertex, lonVertex)
    # create the scrip file
    write_scrip_file(scripFilename, title, nVertices, vertexDegree,
                     latVertex, lonVertex, corner_lat, corner_lon) 
# ---------------------------------------------------------------------
# Private functions
# ---------------------------------------------------------------------
def _rotate_about_vector(vectorToRotate, vectorAxis, rotationAngle):
    """
    Rotate the given Cartesian vector around the given axis by the given
    angle.
    Parameters
    ----------
    vectorToRotate : numpy.ndarray
        The 3-element vector to rotate
    vectorAxis : numpy.ndarray
        The 3-element axis of rotation
    rotationAngle : float
        The angle of rotation (in radians)
    Returns
    -------
    rotatedVector : numpy.ndarray
        The rotated vector
    """
    # http://ksuweb.kennesaw.edu/~plaval//math4490/rotgen.pdf
    r = vectorAxis / np.linalg.norm(vectorAxis)
    C = math.cos(rotationAngle)
    S = math.sin(rotationAngle)
    t = 1.0 - C
    rot = np.zeros((3, 3))
    rot[0, 0] = t * r[0] * r[0] + C
    rot[0, 1] = t * r[0] * r[1] - S * r[2]
    rot[0, 2] = t * r[0] * r[2] + S * r[1]
    rot[1, 0] = t * r[0] * r[1] + S * r[2]
    rot[1, 1] = t * r[1] * r[1] + C
    rot[1, 2] = t * r[1] * r[2] - S * r[0]
    rot[2, 0] = t * r[0] * r[2] - S * r[1]
    rot[2, 1] = t * r[1] * r[2] + S * r[0]
    rot[2, 2] = t * r[2] * r[2] + C
    # get relative position of missing cell
    rotatedVector = np.matmul(rot, vectorToRotate)
    return rotatedVector
def _wrap_index(i, n):
    return i % n
def _rotate_about_vertex(latCell, lonCell, latVertex, lonVertex, angle):
    vertexPosition = _get_position_from_lat_lon(latVertex, lonVertex)
    cellPosition = _get_position_from_lat_lon(latCell, lonCell)
    vertexToCellVector = cellPosition - vertexPosition
    vertexToCellVectorMagnitude = _vector_magnitude(vertexToCellVector)
    vertexToCellVectorNormalized = _normalize_vector(vertexToCellVector)
    perpendicularVector = _cross_product(vertexPosition, vertexToCellVector)
    perpendicularVectorNormalized = _normalize_vector(perpendicularVector)
    rotatedVectorNormalized = \
        math.cos(angle * degreesToRadians) * vertexToCellVectorNormalized + \
        math.sin(angle * degreesToRadians) * perpendicularVectorNormalized
    rotatedVector = rotatedVectorNormalized * vertexToCellVectorMagnitude
    newPositionVector = vertexPosition + rotatedVector
    latRotated, lonRotated = _get_lat_lon_from_position(newPositionVector)
    return latRotated, lonRotated
def _estimate_missing_cell_latlon(
        iVertex, iCellOnVertexNeeded, vertexDegree, cellsOnVertex, latCell,
        lonCell, latVertex, lonVertex):
    """
    Estimate the latitude and longitude of a "missing" neighbor cell that has
    been culled from the MPAS-Seaice mesh
    Parameters
    ----------
    iVertex : int
        The index of the vertex with the missing neighbor
    iCellOnVertexNeeded : int
        The local index on the vertex of the missing cell
    vertexDegree : int
        The maximum number of cells neighboring each vertex
    cellsOnVertex : numpy.ndarray
        The indices of cells neighboring each vertex in the mesh
    latCell : numpy.ndarray
        The latitude (in radians) of cell centers
    lonCell : numpy.ndarray
        The longitude (in radians) of cell centers
    latVertex : numpy.ndarray
        The latitude (in radians) of vertices
    lonVertex : numpy.ndarray
        The longitude (in radians) of vertices
    Returns
    -------
    lat : float
        The approximate latitude of the missing cell center
    lon : float
        The approximate longitude of the missing cell center
    Raises
    ------
    ValueError
        If no reasonable location for the missing cell can be found
    """
    # firstly we find a cell on vertex that exists
    nRotations = 0
    for iCellOnVertex in range(0, vertexDegree):
        iCell = cellsOnVertex[iVertex, _wrap_index(
            iCellOnVertexNeeded - iCellOnVertex - 1, vertexDegree)] - 1
        nRotations = nRotations + 1
        if iCell != -1:
            # find relative vector to cell we have which needs to be rotated
            cellPosition = _get_position_from_lat_lon(
                latCell[iCell], lonCell[iCell])
            vertexPosition = _get_position_from_lat_lon(
                latVertex[iVertex], lonVertex[iVertex])
            cellVector = cellPosition - vertexPosition
            # rotate cell vector around vertex vector
            theta = nRotations * ((2.0 * math.pi) / float(vertexDegree))
            missingCellVector = _rotate_about_vector(
                cellVector, vertexPosition, theta)
            # get absolute position
            missingCellPosition = vertexPosition + missingCellVector
            # get missing cell lat, lon
            lat, lon = _get_lat_lon_from_position(missingCellPosition)
            return lat, lon
    raise ValueError("Can't find position!")
def _get_position_from_lat_lon(lat, lon):
    position = np.zeros(3)
    position[0] = math.cos(lat) * math.cos(lon)
    position[1] = math.cos(lat) * math.sin(lon)
    position[2] = math.sin(lat)
    return position
def _get_lat_lon_from_position(position):
    lat = math.asin(position[2])
    lon = math.atan2(position[1], position[0])
    return lat, lon
def _vector_magnitude(vector):
    magnitude = math.sqrt(math.pow(vector[0], 2) +
                          math.pow(vector[1], 2) +
                          math.pow(vector[2], 2))
    return magnitude
def _normalize_vector(vector):
    magnitude = _vector_magnitude(vector)
    normalizedVector = vector / magnitude
    return normalizedVector
def _cross_product(u, v):
    cp = np.zeros(3)
    cp[0] = u[1] * v[2] - u[2] * v[1]
    cp[1] = u[2] * v[0] - u[0] * v[2]
    cp[2] = u[0] * v[1] - u[1] * v[0]
    return cp