Ejemplo n.º 1
0
def get_data(name, output_directory):

    baseurl = 'http://gsaweb.ast.cam.ac.uk/alerts/alert'

    content = aud.get_file_contents("{}/{}".format(baseurl, name), cache=True)
    htmldoc = bs4.BeautifulSoup(content, 'html5lib')

    for line in htmldoc.strings:
        if 'var spectra' in line:
            spectra_data = line.split('=')[1].strip()

            spectra_data = Table(eval(spectra_data[1:-2]))

    try:
        spectra_meta = Table.read(content, format='ascii.html')
    except InconsistentTableError:
        if "Not found" in content:
            warnings.warn("data is not found for {}, check whether it's a "
                          "valid GaiaAlerts object".format(name))
            return None
    spectra = hstack([spectra_meta, spectra_data], join_type='outer')

    spectra['Name'] = name

    if output_directory is not None:
        spectra.write(os.path.join(output_directory, '{}.fits'.format(name)),
                      overwrite=True)

    return spectra
Ejemplo n.º 2
0
def reload_votable_fields_json():
    content = aud.get_file_contents("http://simbad.u-strasbg.fr/simbad/sim-help?Page=sim-fscript#VotableFields")

    import bs4
    htmldoc = bs4.BeautifulSoup(content, 'html5lib')
    search_text = re.compile(r'Field names for VOTable output', re.IGNORECASE)
    foundtext = htmldoc.find('h2', text=search_text)

    # Find the first <table> tag that follows it
    table = foundtext.findNext('table')
    outd = {}
    for row in table.findAll('tr'):
        cols = row.findChildren('td')
        if len(cols) > 1:
            smallest_child = cols[0].find_all()[-1]
            if cols[0].findChild("ul"):
                text1 = cols[0].findChild('ul').getText()
            elif cols[0].find_all():
                text1 = smallest_child.getText()
            else:
                text1 = cols[0].getText()
            if cols[1].findChild("ul"):
                text2 = cols[1].findChild('ul').getText()
            else:
                text2 = cols[1].getText()
            # ignore blank entries & headers
            if (text2.strip() != '' and not
                (smallest_child.name == 'font' and
                 'size' in smallest_child.attrs and
                 smallest_child.attrs['size'] == '+2')):
                outd[text1.strip()] = text2.strip()

    with open('data/votable_fields_dict.json', 'w') as f:
        json.dump(outd, f, indent=2, sort_keys=True)
Ejemplo n.º 3
0
def get_data(name, output_directory):

    baseurl = 'http://gsaweb.ast.cam.ac.uk/alerts/alert'

    content = aud.get_file_contents("{}/{}".format(baseurl, name), cache=True)
    htmldoc = bs4.BeautifulSoup(content, 'html5lib')

    search_text = re.compile('var spectra')
    line = htmldoc.find('script', text=search_text)

    try:
        spectra_data = line.string.split('=')[1].strip()
    except AttributeError:
        warnings.warn("data is not found for {}, check whether it's a "
                      "valid GaiaAlerts object".format(name))
        return None

    spectra_data = Table(ast.literal_eval(spectra_data[1:-2]))

    spectra_meta = Table.read(content, format='ascii.html')
    spectra = hstack([spectra_meta, spectra_data], join_type='outer')

    spectra['Name'] = name

    if output_directory is not None:
        spectra.write(os.path.join(output_directory, '{}.fits'.format(name)),
                      overwrite=True)

    return spectra
Ejemplo n.º 4
0
def reload_votable_fields_json():
    content = aud.get_file_contents("http://simbad.u-strasbg.fr/simbad/sim-help?Page=sim-fscript#VotableFields")

    import bs4

    htmldoc = bs4.BeautifulSoup(content)
    search_text = re.compile(r"Field names for VOTable output", re.IGNORECASE)
    foundtext = htmldoc.find("h2", text=search_text)

    table = foundtext.findNext("table")  # Find the first <table> tag that follows it
    outd = {}
    for row in table.findAll("tr"):
        cols = row.findChildren("td")
        if len(cols) > 1:
            smallest_child = cols[0].find_all()[-1]
            if cols[0].findChild("ul"):
                text1 = cols[0].findChild("ul").getText()
            elif cols[0].find_all():
                text1 = smallest_child.getText()
            else:
                text1 = cols[0].getText()
            if cols[1].findChild("ul"):
                text2 = cols[1].findChild("ul").getText()
            else:
                text2 = cols[1].getText()
            # ignore blank entries & headers
            if text2.strip() != "" and not (
                smallest_child.name == "font"
                and "size" in smallest_child.attrs
                and smallest_child.attrs["size"] == "+2"
            ):
                outd[text1.strip()] = text2.strip()

    with open("data/votable_fields_dict.json", "w") as f:
        json.dump(outd, f, indent=2, sort_keys=True)
Ejemplo n.º 5
0
def observing_sites():
    """Returns a dict of observatory sites based on astropy's database."""
    jsonurl = 'http://data.astropy.org/coordinates/sites.json'
    js = json.loads(get_file_contents(jsonurl, show_progress=False, cache=True))
    sitedict = {}
    for sitekey, site in js.items():
        sitedict[sitekey] = site['name']
    return sitedict
Ejemplo n.º 6
0
def get_downloaded_sites(jsonurl=None):
    """
    Load observatory database from data.astropy.org and parse into a SiteRegistry
    """

    # we explicitly set the encoding because the default is to leave it set by
    # the users' locale, which may fail if it's not matched to the sites.json
    if jsonurl is None:
        content = get_pkg_data_contents('coordinates/sites.json', encoding='UTF-8')
    else:
        content = get_file_contents(jsonurl, encoding='UTF-8')

    jsondb = json.loads(content)
    return SiteRegistry.from_json(jsondb)
Ejemplo n.º 7
0
def get_downloaded_sites(jsonurl=None):
    """
    Load observatory database from data.astropy.org and parse into a SiteRegistry
    """

    # we explicitly set the encoding because the default is to leave it set by
    # the users' locale, which may fail if it's not matched to the sites.json
    if jsonurl is None:
        content = get_pkg_data_contents('coordinates/sites.json',
                                        encoding='UTF-8')
    else:
        content = get_file_contents(jsonurl, encoding='UTF-8')

    jsondb = json.loads(content)
    return SiteRegistry.from_json(jsondb)
Ejemplo n.º 8
0
def main(project=None, unit=None, folders_file=None, parallel=False, remote=False, remove_after=False, verbose=False, **kwargs):

    pan = Panoptes(simulator=['all'])

    folders = get_file_contents(folders_file).strip().split('\n')

    # See if the remote path exists in the HDF5 data store
    store = pd.HDFStore(kwargs.get('hdf5_file'))

    with console.ProgressBarOrSpinner(len(folders), "Folders") as bar:
        for idx, folder in enumerate(folders):
            folder = folder.rstrip('/')

            hdf_path = '/observing/{}'.format(folder)

            if hdf_path not in store.keys():

                local_dir = '/var/panoptes/images/fields/{}/'.format(folder)

                if not os.path.exists(local_dir) and remote:
                    # Get the data
                    remote_path = 'gs://{}/{}/{}'.format(project, unit, folder)
                    get_remote_dir(remote_path, local_dir=local_dir, extension='cr2')

                # Make data
                make_pec_data(folder, observer=pan.observatory.scheduler, parallel=parallel, verbose=verbose)

                if remove_after:
                    # Remove the data
                    try:
                        shutil.rmtree(local_dir)
                    except Exception as e:
                        if verbose:
                            print("Error removing dir: {}".format(e))
            else:
                if verbose:
                    print("{} already in HDF5 table".format(folder))

            bar.update(idx)
Ejemplo n.º 9
0
def get_data(name, output):

    baseurl = 'http://gsaweb.ast.cam.ac.uk/alerts/alert'

    content = aud.get_file_contents("{}/{}".format(baseurl, name), cache=True)
    htmldoc = bs4.BeautifulSoup(content, 'html5lib')

    for line in htmldoc.strings:
        if 'var spectra' in line:
            spectra_data = line.split('=')[1].strip()

            spectra_data = Table(eval(spectra_data[1:-2]))

    spectra_meta = Table.read(content, format='ascii.html')

    spectra = hstack([spectra_meta, spectra_data], join_type='outer')

    if output is not None:
        spectra.write(os.path.join(output, '{}.fits'.format(name)),
                      overwrite=True)

    return spectra
Ejemplo n.º 10
0
def reload_votable_fields_json():
    content = aud.get_file_contents(
        "http://simbad.u-strasbg.fr/simbad/sim-help?Page=sim-fscript#VotableFields"
    )

    import bs4
    htmldoc = bs4.BeautifulSoup(content, 'html5lib')
    search_text = re.compile(r'Field names for VOTable output', re.IGNORECASE)
    foundtext = htmldoc.find('h2', text=search_text)

    # Find the first <table> tag that follows it
    table = foundtext.findNext('table')
    outd = {}
    for row in table.findAll('tr'):
        cols = row.findChildren('td')
        if len(cols) > 1:
            smallest_child = cols[0].find_all()[-1]
            if cols[0].findChild("ul"):
                text1 = cols[0].findChild('ul').getText()
            elif cols[0].find_all():
                text1 = smallest_child.getText()
            else:
                text1 = cols[0].getText()
            if cols[1].findChild("ul"):
                text2 = cols[1].findChild('ul').getText()
            else:
                text2 = cols[1].getText()
            # ignore blank entries & headers
            if (text2.strip() != ''
                    and not (smallest_child.name == 'font'
                             and 'size' in smallest_child.attrs
                             and smallest_child.attrs['size'] == '+2')):
                outd[text1.strip()] = text2.strip()

    with open('data/votable_fields_dict.json', 'w') as f:
        json.dump(outd, f, indent=2, sort_keys=True)
Ejemplo n.º 11
0
def get_icrs_coordinates(name, parse=False, cache=False):
    """
    Retrieve an ICRS object by using an online name resolving service to
    retrieve coordinates for the specified name. By default, this will
    search all available databases until a match is found. If you would like
    to specify the database, use the science state
    ``astropy.coordinates.name_resolve.sesame_database``. You can also
    specify a list of servers to use for querying Sesame using the science
    state ``astropy.coordinates.name_resolve.sesame_url``. This will try
    each one in order until a valid response is returned. By default, this
    list includes the main Sesame host and a mirror at vizier.  The
    configuration item `astropy.utils.data.Conf.remote_timeout` controls the
    number of seconds to wait for a response from the server before giving
    up.

    Parameters
    ----------
    name : str
        The name of the object to get coordinates for, e.g. ``'M42'``.
    parse : bool
        Whether to attempt extracting the coordinates from the name by
        parsing with a regex. For objects catalog names that have
        J-coordinates embedded in their names eg:
        'CRTS SSS100805 J194428-420209', this may be much faster than a
        sesame query for the same object name. The coordinates extracted
        in this way may differ from the database coordinates by a few
        deci-arcseconds, so only use this option if you do not need
        sub-arcsecond accuracy for coordinates.
    cache : bool, str, optional
        Determines whether to cache the results or not. Passed through to
        `~astropy.utils.data.download_file`, so pass "update" to update the
        cached value.

    Returns
    -------
    coord : `astropy.coordinates.ICRS` object
        The object's coordinates in the ICRS frame.

    """

    # if requested, first try extract coordinates embedded in the object name.
    # Do this first since it may be much faster than doing the sesame query
    if parse:
        from . import jparser
        if jparser.search(name):
            return jparser.to_skycoord(name)
        else:
            # if the parser failed, fall back to sesame query.
            pass
            # maybe emit a warning instead of silently falling back to sesame?

    database = sesame_database.get()
    # The web API just takes the first letter of the database name
    db = database.upper()[0]

    # Make sure we don't have duplicates in the url list
    urls = []
    domains = []
    for url in sesame_url.get():
        domain = urllib.parse.urlparse(url).netloc

        # Check for duplicates
        if domain not in domains:
            domains.append(domain)

            # Add the query to the end of the url, add to url list
            fmt_url = os.path.join(url, "{db}?{name}")
            fmt_url = fmt_url.format(name=urllib.parse.quote(name), db=db)
            urls.append(fmt_url)

    exceptions = []
    for url in urls:
        try:
            resp_data = get_file_contents(
                download_file(url, cache=cache, show_progress=False))
            break
        except urllib.error.URLError as e:
            exceptions.append(e)
            continue
        except socket.timeout as e:
            # There are some cases where urllib2 does not catch socket.timeout
            # especially while receiving response data on an already previously
            # working request
            e.reason = ("Request took longer than the allowed "
                        f"{data.conf.remote_timeout:.1f} seconds")
            exceptions.append(e)
            continue

    # All Sesame URL's failed...
    else:
        messages = [f"{url}: {e.reason}" for url, e in zip(urls, exceptions)]
        raise NameResolveError("All Sesame queries failed. Unable to "
                               "retrieve coordinates. See errors per URL "
                               f"below: \n {os.linesep.join(messages)}")

    ra, dec = _parse_response(resp_data)

    if ra is None or dec is None:
        if db == "A":
            err = f"Unable to find coordinates for name '{name}' using {url}"
        else:
            err = f"Unable to find coordinates for name '{name}' in database {database} using {url}"

        raise NameResolveError(err)

    # Return SkyCoord object
    sc = SkyCoord(ra=ra, dec=dec, unit=(u.degree, u.degree), frame='icrs')
    return sc
Ejemplo n.º 12
0
def load_mccon12_table(mcconn_url='https://www.astrosci.ca/users/alan/Nearby_Dwarfs_Database_files/NearbyGalaxies.dat', qtable=True):
    from astropy.utils import data
    from astropy.io import ascii

    # have to do SSL stuff because astrosci.ca has expired SSL
    import ssl
    from urllib.error import URLError

    baseline_create = ssl._create_default_https_context
    try:
        mcconn_tab_str = data.get_file_contents(mcconn_url, cache=True)
    except URLError as e:
        ee[0] = e
        if 'SSL: CERTIFICATE_VERIFY_FAILED' in str(e.args):
            ssl._create_default_https_context = ssl._create_unverified_context
            exec(toexec)
        else:
            raise
    finally:
        ssl._create_default_https_context = baseline_create


    headerrow = mcconn_tab_str.split('\n')[32]
    colnames = headerrow.split()
    colidxs = [headerrow.rindex(col) for col in colnames]

    # this *removes* the references
    col_starts = colidxs[:-1]
    col_ends = [i-1 for i in colidxs[1:]]
    colnames = colnames[:-1]

    str_tab = ascii.read(mcconn_tab_str.split('\n')[34:], format='fixed_width_no_header',
                         names=colnames, col_starts=col_starts, col_ends=col_ends)

    mcconn_tab = (QTable if qtable else Table)()
    mcconn_tab['Name'] = [s.strip() for s in str_tab['GalaxyName']]

    scs = []
    for row in str_tab:
        dm = float(row['(m-M)o'].split()[0])
        scs.append(SkyCoord(row['RA'], row['Dec'], unit=(u.hour, u.deg),
                            distance=Distance(distmod=dm)))
    mcconn_tab['Coords'] = SkyCoord(scs)

    for col in str_tab.colnames[3:]:
        if col in ('EB-V', 'F', 'MHI'):
            #single number
            mcconn_tab[col] = [float(s) for s in str_tab[col]]
        else:
            # num + -
            vals, ps, ms = [], [], []
            for s in str_tab[col]:
                val, p, m = s.split()
                vals.append(float(val))
                ps.append(float(p))
                ms.append(float(m))
            mcconn_tab[col] = vals
            mcconn_tab[col + '+'] = ps
            mcconn_tab[col + '-'] = ms

    return mcconn_tab
Ejemplo n.º 13
0
def load_mccon12_table(
        mcconn_url='https://www.astrosci.ca/users/alan/Nearby_Dwarfs_Database_files/NearbyGalaxies.dat',
        qtable=True):
    from astropy.utils import data
    from astropy.io import ascii

    # have to do SSL stuff because astrosci.ca has expired SSL
    import ssl
    from urllib.error import URLError

    baseline_create = ssl._create_default_https_context
    try:
        mcconn_tab_str = data.get_file_contents(mcconn_url, cache=True)
    except URLError as e:
        ee[0] = e
        if 'SSL: CERTIFICATE_VERIFY_FAILED' in str(e.args):
            ssl._create_default_https_context = ssl._create_unverified_context
            exec(toexec)
        else:
            raise
    finally:
        ssl._create_default_https_context = baseline_create

    headerrow = mcconn_tab_str.split('\n')[32]
    colnames = headerrow.split()
    colidxs = [headerrow.rindex(col) for col in colnames]

    # this *removes* the references
    col_starts = colidxs[:-1]
    col_ends = [i - 1 for i in colidxs[1:]]
    colnames = colnames[:-1]

    str_tab = ascii.read(mcconn_tab_str.split('\n')[34:],
                         format='fixed_width_no_header',
                         names=colnames,
                         col_starts=col_starts,
                         col_ends=col_ends)

    mcconn_tab = (QTable if qtable else Table)()
    mcconn_tab['Name'] = [s.strip() for s in str_tab['GalaxyName']]

    scs = []
    for row in str_tab:
        dm = float(row['(m-M)o'].split()[0])
        scs.append(
            SkyCoord(row['RA'],
                     row['Dec'],
                     unit=(u.hour, u.deg),
                     distance=Distance(distmod=dm)))
    mcconn_tab['Coords'] = SkyCoord(scs)

    for col in str_tab.colnames[3:]:
        if col in ('EB-V', 'F', 'MHI'):
            #single number
            mcconn_tab[col] = [float(s) for s in str_tab[col]]
        else:
            # num + -
            vals, ps, ms = [], [], []
            for s in str_tab[col]:
                val, p, m = s.split()
                vals.append(float(val))
                ps.append(float(p))
                ms.append(float(m))
            mcconn_tab[col] = vals
            mcconn_tab[col + '+'] = ps
            mcconn_tab[col + '-'] = ms

    return mcconn_tab
Ejemplo n.º 14
0
def get_mcconn_table(fnorurl='http://www.astro.uvic.ca/~alan/Nearby_Dwarfs_Database_files/NearbyGalaxies.dat', dropmw=True, sanitizenames=False):
    """
    Gets the Mcconnachie 12 table from the file.

    Parameters
    ----------
    fnorurl : str, optional
        Local file or URL.  If URL, will be cached.
    dropmw : bool, optional
        If true, the entry for the MW will be skipped.
    sanitizenames : bool, optional
        If True, names like "gal (I)" will be changed to "gal I"

    Returns
    -------
    mctab : astropy.table.Table
        The Mcconnachie 12 "database" as an astropy table
    """
    import os

    from astropy.coordinates import Angle, ICRS, SkyCoord
    from astropy.table import Table, Column
    from astropy.utils import data
    from astropy import units as u

    if fnorurl is None:
        if os.path.isfile('NearbyGalaxies.dat'):
            fnorurl = 'NearbyGalaxies.dat'
        else:
            fnorurl = 'https://www.astrosci.ca/users/alan/Nearby_Dwarfs_Database_files/NearbyGalaxies.dat'

    datstr = data.get_file_contents(fnorurl, cache=True)
    datlines = datstr.split('\n')

    # pull from the header.
    for hdri, hdrstr in enumerate(datlines):
        if hdrstr.startswith('GalaxyName'):
            break
    else:
        raise ValueError('No field name line found')
    # hdrstr now is the line with the field names

    # type is 'pm' for float w/ err+/- or 'coord'
    fieldinfo = [('name', 'S19', None),
                 ('center', 'coord', None),
                 ('EBmV', float, u.mag),
                 ('distmod', 'pm', u.mag),
                 ('vh', 'pm', u.km / u.s),
                 ('Vmag', 'pm', u.mag),
                 ('PA', 'pm', u.degree),
                 ('e', 'pm', u.dimensionless_unscaled),
                 ('muV0', 'pm', u.mag * u.arcsec ** -2),
                 ('rh', 'pm', u.arcmin),
                 ('sigma_s', 'pm', u.km / u.s),
                 ('vrot_s', 'pm', u.km / u.s),
                 ('MHI', float, u.Unit('1e6 solMass')),
                 ('sigma_g', 'pm', u.km / u.s),
                 ('vrot_g', 'pm', u.km / u.s),
                 ('[Fe/H]', 'pm', u.dimensionless_unscaled),
                 ('F', int, None),
                 ('References', 'S40', None)]

    fieldnames = []
    fielddtypes = []
    fieldunits = []
    for nm, tp, un in fieldinfo:
        if tp == 'coord':
            fieldnames.append(nm)
            fielddtypes.append(object)
            fieldunits.append(None)
        elif tp == 'pm':
            fieldnames.append(nm)
            fielddtypes.append(float)
            fieldunits.append(un)
            fieldnames.append(nm + '+')
            fielddtypes.append(float)
            fieldunits.append(un)
            fieldnames.append(nm + '-')
            fielddtypes.append(float)
            fieldunits.append(un)
        else:
            fieldnames.append(nm)
            fielddtypes.append(tp)
            fieldunits.append(un)

    t = Table(names=fieldnames, dtype=fielddtypes)
    for nm, un in zip(fieldnames, fieldunits):
        t[nm].units = un

    for l in datlines[(hdri + 2 + int(dropmw)):]:
        if l.strip() == '':
            continue

        vals = l[19:].split()
        ra = Angle(tuple([float(v) for v in vals[0:3]]), unit=u.hour)
        dec = Angle(tuple([float(v) for v in vals[3:6]]), unit=u.degree)
        vals = vals[6:]
        vals.insert(0, ICRS(ra, dec))
        vals.insert(0, l[:19].strip())
        if '(' not in vals[-1]:
            vals.append('')
        t.add_row(vals)

    # now add derived columns
    t.add_column(Column(name='Vabs', data=t['Vmag'] - t['distmod'], unit=u.mag))  # Vmag apparently already includes dereddening?
    t.add_column(Column(name='logLV', data=(t['Vabs'] - 4.83) / -2.5, unit=u.solLum))
    t.add_column(Column(name='distance', data=10 ** (t['distmod'] / 5. - 2), unit=u.kpc))
    t.add_column(Column(name='distance+', data=t['distmod+'] * t['distance'] * np.log(10) / 5, unit=u.kpc))
    t.add_column(Column(name='distance-', data=t['distmod-'] * t['distance'] * np.log(10) / 5, unit=u.kpc))
    t.add_column(Column(name='rh_phys', data=t['distance'] * np.radians(t['rh'] / 60.), unit=u.kpc))
    t.add_column(Column(name='radeg', data=[((ti.ra.degree + 180) % 360 - 180) for ti in t['center']], unit=u.degree))
    t.add_column(Column(name='decdeg', data=[ti.dec.degree for ti in t['center']], unit=u.degree))

    # Now replace the 'center' with a version that includes the distance.
    newscs = []
    xs = []
    ys = []
    zs = []
    for ic in t['center']:
        newscs.append(SkyCoord(ic.ra, ic.dec, distance=t['distance']))
        xs.append(newscs[-1].cartesian.x)
        ys.append(newscs[-1].cartesian.y)
        zs.append(newscs[-1].cartesian.z)
    t.add_column(Column(name='x', data=xs, unit=u.kpc))
    t.add_column(Column(name='y', data=ys, unit=u.kpc))
    t.add_column(Column(name='z', data=zs, unit=u.kpc))

    # andromeda dSph numbers
    andnum = []
    for nm in t['name']:
        if nm.startswith('Andromeda '):
            andnum.append(roman_to_int(nm.replace('Andromeda ', '')))
        elif nm == 'Andromeda':
            andnum.append(0)
        elif nm in ('M32', 'NGC 205', 'NGC 185', 'NGC 147'):
            andnum.append(-1)
        elif nm in ('IC 10', 'LGS 3'):
            andnum.append(-2)
        elif nm in ('IC 1613', 'Pegasus dIrr'):
            andnum.append(-3)
        else:
            andnum.append(-99)
    t.add_column(Column(name='and_number', data=andnum, unit=None))

    if sanitizenames:
        t['name'] = [nm.replace('(I)', 'I') for nm in t['name']]

    return t
Ejemplo n.º 15
0
def get_mcconn_table(
        fnorurl='http://www.astro.uvic.ca/~alan/Nearby_Dwarfs_Database_files/NearbyGalaxies.dat',
        dropmw=True,
        sanitizenames=False):
    """
    Gets the Mcconnachie 12 table from the file.

    Parameters
    ----------
    fnorurl : str, optional
        Local file or URL.  If URL, will be cached.
    dropmw : bool, optional
        If true, the entry for the MW will be skipped.
    sanitizenames : bool, optional
        If True, names like "gal (I)" will be changed to "gal I"

    Returns
    -------
    mctab : astropy.table.Table
        The Mcconnachie 12 "database" as an astropy table
    """
    import os

    from astropy.coordinates import Angle, ICRS, SkyCoord
    from astropy.table import Table, Column
    from astropy.utils import data
    from astropy import units as u

    if fnorurl is None:
        if os.path.isfile('NearbyGalaxies.dat'):
            fnorurl = 'NearbyGalaxies.dat'
        else:
            fnorurl = 'https://www.astrosci.ca/users/alan/Nearby_Dwarfs_Database_files/NearbyGalaxies.dat'

    datstr = data.get_file_contents(fnorurl, cache=True)
    datlines = datstr.split('\n')

    # pull from the header.
    for hdri, hdrstr in enumerate(datlines):
        if hdrstr.startswith('GalaxyName'):
            break
    else:
        raise ValueError('No field name line found')
    # hdrstr now is the line with the field names

    # type is 'pm' for float w/ err+/- or 'coord'
    fieldinfo = [('name', 'S19', None), ('center', 'coord', None),
                 ('EBmV', float, u.mag), ('distmod', 'pm', u.mag),
                 ('vh', 'pm', u.km / u.s), ('Vmag', 'pm', u.mag),
                 ('PA', 'pm', u.degree), ('e', 'pm', u.dimensionless_unscaled),
                 ('muV0', 'pm', u.mag * u.arcsec**-2), ('rh', 'pm', u.arcmin),
                 ('sigma_s', 'pm', u.km / u.s), ('vrot_s', 'pm', u.km / u.s),
                 ('MHI', float, u.Unit('1e6 solMass')),
                 ('sigma_g', 'pm', u.km / u.s), ('vrot_g', 'pm', u.km / u.s),
                 ('[Fe/H]', 'pm', u.dimensionless_unscaled), ('F', int, None),
                 ('References', 'S40', None)]

    fieldnames = []
    fielddtypes = []
    fieldunits = []
    for nm, tp, un in fieldinfo:
        if tp == 'coord':
            fieldnames.append(nm)
            fielddtypes.append(object)
            fieldunits.append(None)
        elif tp == 'pm':
            fieldnames.append(nm)
            fielddtypes.append(float)
            fieldunits.append(un)
            fieldnames.append(nm + '+')
            fielddtypes.append(float)
            fieldunits.append(un)
            fieldnames.append(nm + '-')
            fielddtypes.append(float)
            fieldunits.append(un)
        else:
            fieldnames.append(nm)
            fielddtypes.append(tp)
            fieldunits.append(un)

    t = Table(names=fieldnames, dtype=fielddtypes)
    for nm, un in zip(fieldnames, fieldunits):
        t[nm].units = un

    for l in datlines[(hdri + 2 + int(dropmw)):]:
        if l.strip() == '':
            continue

        vals = l[19:].split()
        ra = Angle(tuple([float(v) for v in vals[0:3]]), unit=u.hour)
        dec = Angle(tuple([float(v) for v in vals[3:6]]), unit=u.degree)
        vals = vals[6:]
        vals.insert(0, ICRS(ra, dec))
        vals.insert(0, l[:19].strip())
        if '(' not in vals[-1]:
            vals.append('')
        t.add_row(vals)

    # now add derived columns
    t.add_column(
        Column(name='Vabs', data=t['Vmag'] - t['distmod'],
               unit=u.mag))  # Vmag apparently already includes dereddening?
    t.add_column(
        Column(name='logLV', data=(t['Vabs'] - 4.83) / -2.5, unit=u.solLum))
    t.add_column(
        Column(name='distance', data=10**(t['distmod'] / 5. - 2), unit=u.kpc))
    t.add_column(
        Column(name='distance+',
               data=t['distmod+'] * t['distance'] * np.log(10) / 5,
               unit=u.kpc))
    t.add_column(
        Column(name='distance-',
               data=t['distmod-'] * t['distance'] * np.log(10) / 5,
               unit=u.kpc))
    t.add_column(
        Column(name='rh_phys',
               data=t['distance'] * np.radians(t['rh'] / 60.),
               unit=u.kpc))
    t.add_column(
        Column(name='radeg',
               data=[((ti.ra.degree + 180) % 360 - 180) for ti in t['center']],
               unit=u.degree))
    t.add_column(
        Column(name='decdeg',
               data=[ti.dec.degree for ti in t['center']],
               unit=u.degree))

    # Now replace the 'center' with a version that includes the distance.
    newscs = []
    xs = []
    ys = []
    zs = []
    for ic in t['center']:
        newscs.append(SkyCoord(ic.ra, ic.dec, distance=t['distance']))
        xs.append(newscs[-1].cartesian.x)
        ys.append(newscs[-1].cartesian.y)
        zs.append(newscs[-1].cartesian.z)
    t.add_column(Column(name='x', data=xs, unit=u.kpc))
    t.add_column(Column(name='y', data=ys, unit=u.kpc))
    t.add_column(Column(name='z', data=zs, unit=u.kpc))

    # andromeda dSph numbers
    andnum = []
    for nm in t['name']:
        if nm.startswith('Andromeda '):
            andnum.append(roman_to_int(nm.replace('Andromeda ', '')))
        elif nm == 'Andromeda':
            andnum.append(0)
        elif nm in ('M32', 'NGC 205', 'NGC 185', 'NGC 147'):
            andnum.append(-1)
        elif nm in ('IC 10', 'LGS 3'):
            andnum.append(-2)
        elif nm in ('IC 1613', 'Pegasus dIrr'):
            andnum.append(-3)
        else:
            andnum.append(-99)
    t.add_column(Column(name='and_number', data=andnum, unit=None))

    if sanitizenames:
        t['name'] = [nm.replace('(I)', 'I') for nm in t['name']]

    return t