Takes an EnvironmentFile and a 2d array represemtimg phenotypes at each
    location. Optionally also takes an integer indicating the maximum number
    of clusters allowed to be created (default 15).

    Environment is expected to already have been converted to binary numbers
    (generally because this is being called by rank_environment_and_phenotypes)

    Return a dictionary mapping binary strings representing groups of
    resources/tasks that are present/performed in a given cell to integers
    indicating the ranked order of the cluster they're part of.

def get_ranks_for_environment_and_phenotypes(environment, phenotypes, k=15):
    """
    Takes an EnvironmentFile and a 2d array represemtimg phenotypes at each
    location. Optionally also takes an integer indicating the maximum number
    of clusters allowed to be created (default 15).

    Environment is expected to already have been converted to binary numbers
    (generally because this is being called by rank_environment_and_phenotypes)

    Return a dictionary mapping binary strings representing groups of
    resources/tasks that are present/performed in a given cell to integers
    indicating the ranked order of the cluster they're part of.
    """
    # Create list of all niches and all phenotypes, in phenotype format
    niches = flatten_array(environment)
    phenotypes = flatten_array(phenotypes)

    types = set(phenotypes+niches)

    types.discard("-0b1")  # We'll handle this specially
    types.discard("0b0")  # We'll handle this specially

    # Do all clustering ahead of time so colors remain consistent.
    ranks = generate_ranks(list(types), k)

    ranks["-0b1"] = -1  # The empty phenotype/niche should always be rank -1
    ranks["0b0"] = 0  # The empty phenotype/niche should always be rank 0

    return ranks