def join_src_opsim(src_df, opsim_df, save_path=None):
    """
    Parameters
    ----------
    src_df : Pandas.DataFrame
    opsim_df : Pandas.DataFrame
    save_to_disk : str
    
    Note
    ----
    In OpSim, obsHistID and filter combinations are unique, i.e.
    >> opsim.groupby(['obsHistID', 'filter']).size().prod() == 1
    The tracts mapping table is not used.
    
    Returns
    -------
    DataFrame of Source catalog joined with the OpSim DB and a dictionary
    containing column names from OpSim and Source catalogs
    """
    opsim_cols = opsim_df.columns.values
    src_cols = src_df.columns.values
    metadata = {'opsim_cols': opsim_cols, 'src_cols': src_cols}
    joined = src_df.merge(opsim_df, how='inner', left_on=['visit', 'filter'], right_on=['obsHistID', 'filter'], validate='m:1')
    joined['n_obs'] = joined['objectId'].map(joined['objectId'].value_counts()) # Number of visits for each object
    if save_path is not None:
        joined.to_csv(save_path, index=False)
    return joined, metadata