docs: def read_asc_grid(filename, footer=0):
      """Reads ASCII grid file (*.asc).
  
      Parameters
      ----------
      filename : str
          Name of *.asc file.
      footer : int, optional
          Number of lines at bottom of *.asc file to skip.
  
      Returns
      -------
      grid_array : numpy array, shape (M, N)
          (M, N) array of grid values, where M is number of Y-coordinates and
          N is number of X-coordinates. The array entry corresponding to
          the lower-left coordinates is at index [M, 0], so that
          the array is oriented as it would be in X-Y space.
      x : numpy array, shape (N,)
          1D array of N X-coordinates.
      y : numpy array, shape (M,)
          1D array of M Y-coordinates.
      CELLSIZE : tuple or float
          Either a two-tuple of (x-cell size, y-cell size),
          or a float that specifies the uniform cell size.
      NODATA : float
          Value that specifies which entries are not actual data.
      """
  
      ncols = None
      nrows = None
      xllcorner = None
      xllcenter = None
      yllcorner = None
      yllcenter = None
      cellsize = None
      dx = None
      dy = None
      no_data = None
      header_lines = 0
      with io.open(filename, 'r') as f:
          while True:
              string, value = f.readline().split()
              header_lines += 1
              if string.lower() == 'ncols':
                  ncols = int(value)
              elif string.lower() == 'nrows':
                  nrows = int(value)
              elif string.lower() == 'xllcorner':
                  xllcorner = float(value)
              elif string.lower() == 'xllcenter':
                  xllcenter = float(value)
              elif string.lower() == 'yllcorner':
                  yllcorner = float(value)
              elif string.lower() == 'yllcenter':
                  yllcenter = float(value)
              elif string.lower() == 'cellsize':
                  cellsize = float(value)
              elif string.lower() == 'cell_size':
                  cellsize = float(value)
              elif string.lower() == 'dx':
                  dx = float(value)
              elif string.lower() == 'dy':
                  dy = float(value)
              elif string.lower() == 'nodata_value':
                  no_data = float(value)
              elif string.lower() == 'nodatavalue':
                  no_data = float(value)
              else:
                  raise IOError("could not read *.asc file. Error in header.")
  
              if (ncols is not None) and \
                 (nrows is not None) and \
                 (((xllcorner is not None) and (yllcorner is not None)) or
                  ((xllcenter is not None) and (yllcenter is not None))) and \
                 ((cellsize is not None) or ((dx is not None) and (dy is not None))) and \
                 (no_data is not None):
                  break
  
      raw_grid_array = np.genfromtxt(filename, skip_header=header_lines,
                                     skip_footer=footer)
      grid_array = np.flipud(raw_grid_array)
  
      if nrows != grid_array.shape[0] or ncols != grid_array.shape[1]:
          raise IOError("Error reading *.asc file. Encountered problem "
                        "with header: NCOLS and/or NROWS does not match "
                        "number of columns/rows in data file body.")
  
      if xllcorner is not None and yllcorner is not None:
          if dx is not None and dy is not None:
              xllcenter = xllcorner + dx/2.0
              yllcenter = yllcorner + dy/2.0
          else:
              xllcenter = xllcorner + cellsize/2.0
              yllcenter = yllcorner + cellsize/2.0
  
      if dx is not None and dy is not None:
          x = np.arange(xllcenter, xllcenter + ncols*dx, dx)
          y = np.arange(yllcenter, yllcenter + nrows*dy, dy)
      else:
          x = np.arange(xllcenter, xllcenter + ncols*cellsize, cellsize)
          y = np.arange(yllcenter, yllcenter + nrows*cellsize, cellsize)
  
      # Sometimes x and y and can be an entry too long due to imprecision
      # in calculating the upper cutoff for np.arange(); this bit takes care of
      # that potential problem.
      if x.size == ncols + 1:
          x = x[:-1]
      if y.size == nrows + 1:
          y = y[:-1]
  
      if cellsize is None:
          cellsize = (dx, dy)
  
      return grid_array, x, y, cellsize, no_data