<reponame>AlexEidt/uwtools<filename>uwtools/parse_buildings.py
"""Parses UW's Facilities Websites to get all Building Names"""

import re, json, os, requests
from bs4 import BeautifulSoup
from zlib import decompress
from pkgutil import get_data
import concurrent.futures as cf

dorm_site_re = re.compile(r'\([A-Z]{3,}\)\s?((\</div\>)|(\s?\| Campus Maps))')
dorm_abb_re = re.compile(r'\([A-Z]{3,}\)')

def seattle():
    """
    Parses UW's Facilities to get all Building Names for UW Seattle Campus

    Returns

        Dictionary with Building Name Abbreviations to full Building Names
    """
    def dorms():
        local_dorm_site_re = dorm_site_re
        local_dorm_abb_re = dorm_abb_re
        names = []
        # Get UW Seattle Dorm Building Abbreviations and Names
        uw_dorms = BeautifulSoup(requests.get('https://hfs.uw.edu/Live/Undergraduate-Residence-Halls-and-Apartments').text, 
                            features='lxml')
        for img in uw_dorms.find_all('img'):
            dorm_name = str(img).rsplit('alt="', 1)[-1].split('"', 1)[0]
            if 'Hall' in dorm_name:
                dorm_site = BeautifulSoup(requests.get(f'https://www.google.dz/search?q=http://www.washington.edu/maps UW {dorm_name}').text, 
                                        features='lxml').find('html')
                match = re.search(local_dorm_site_re, str(dorm_site))
                if match:
                    dorm_abb = re.search(local_dorm_abb_re, match.group(0)).group(0)[1:-1]
                    names.append((dorm_abb, dorm_name))
        return names

    def classrooms():
        # Get UW Seattle Classroom Building Abbreviations and Names
        names = []
        uw_classrooms = BeautifulSoup(requests.get('https://www.washington.edu/classroom/').text, 
                                    features='lxml')
        uw_classrooms = uw_classrooms.find("div", {"id": "buildings"})
        for link in uw_classrooms.find_all('a'):
            if link.get('href') not in buildings_dict:
                names.append((link.get('href'), link.text.rsplit('(', 1)[0].strip()))
        return names

    def buildings():
        # Supplement previous UW Building scrape with additional data
        names = []
        buildings = BeautifulSoup(requests.get('https://www.washington.edu/students/reg/buildings.html').text,
                                features='lxml')
        buildings = str(buildings.html).split('<h2>Code - Building Name (Map Grid)</h2>', 1)[-1]
        buildings = BeautifulSoup(buildings.rsplit('<div class="uw-footer">', 1)[0], features='lxml')
        for building_group in buildings.find_all('p'):
            for building in str(building_group).split('<br/>'):
                if 'a href' in building:
                    building = BeautifulSoup(building, features='lxml')
                    abbreviation = building.find('code').text
                    name = building.find('a').text
                    names.append((abbreviation, name.split('\n', 1)[0].strip()))
        return names
    
    buildings_dict = {}
    functions = [dorms, classrooms, buildings]
    with cf.ThreadPoolExecutor() as executor:
        results = [executor.submit(f) for f in functions]
        for f in cf.as_completed(results):
            for key, value in f.result():
                if key not in buildings_dict:
                    buildings_dict[key] = value
    return buildings_dict


def bothell():
    """
    Parses UW's Facilities to get all Building Names for UW Bothell Campus

    Returns

        Dictionary with Building Name Abbreviations to full Building Names
    """
    buildings = {}
    bothell_buildings = BeautifulSoup(requests.get('https://www.uwb.edu/safety/hours').text, features='lxml')
    for building in bothell_buildings.find_all('div', {'class': ['col1', 'col2', 'col3']}):
        bld = str(building.find('h3'))
        if bld and '(' in bld:
            abbreviation = bld.rsplit('(', 1)[-1].split(')')[0]
            name = bld.rsplit('(', 1)[0].strip()
            buildings[abbreviation] = name.replace('<h3>', '').replace('&amp;', '&')
    return buildings

building_re = re.compile(r'[A-Z]{2,} \d+')

def tacoma():
    """
    Parses UW's Facilities to get all Building Names for UW Seattle Campus

    Returns

        Dictionary with Building Name Abbreviations to full Building Names
    """
    buildings = {}
    local_building_re = building_re
    tacoma_buildings = BeautifulSoup(requests.get('https://www.tacoma.uw.edu/campus-map/buildings').text, 
                                features='lxml')
    for building in tacoma_buildings.find('div', class_='field-items').find('ul').find_all('a'):
        text = building.text
        if '(' in text:
            abbreviation = text.rsplit('(', 1)[-1].split(')')[0]
            name = text.rsplit('(', 1)[0].strip()
            buildings[abbreviation] = name
        else:
            link = BeautifulSoup(requests.get('https://www.tacoma.uw.edu{}'.format(str(building.get('href')))).text,
                                            features='lxml')
            for table in link.find_all('table'):
                for l in table.find_all('a'):
                    if re.search(local_building_re, l.text):
                        buildings[str(l.text).split(' ', 1)[0].upper()] = text
                        break
    return buildings


def get_buildings(campuses=['Seattle', 'Bothell', 'Tacoma']):
    """
    Returns the Buildings at UW for each campus

    @params

        'campuses': The Campuses to get the Buildings from

    Returns

        A dictionary with building name abbreviations to full names.
    """
    assert all([c in ['Seattle', 'Bothell', 'Tacoma'] for c in list(map(str.title, campuses))])
    buildings = {}
    functions = [seattle, bothell, tacoma]
    campuses = [function for function in functions if function.__name__.title() in campuses]
    with cf.ThreadPoolExecutor() as executor:
        results = [executor.submit(campus) for campus in campuses]
        for f in cf.as_completed(results):
            for key, value in f.result().items():
                buildings[key] = value
    return buildings


def geocode(buildings=[], campuses=['Seattle', 'Bothell', 'Tacoma']):
    """
    Geocodes UW Buildings

    @params

        'buildings': List of buildings to geocode

        'campuses': Campuses to get building coordinates from

    Returns

        A dictionary with data for each building in the buildings list
    """
    assert all([c in ['Seattle', 'Bothell', 'Tacoma'] for c in list(map(str.title, campuses))])
    coordinates = json.loads(decompress(get_data(__package__, 'Coordinates.file')))
    all_campuses = {}
    for campus, buildings_ in coordinates.items():
        if campus.title() in campuses:
            for building, coords in buildings_.items():
                if building in buildings or not buildings:
                    all_campuses[building] = coords
    return all_campuses
"""Parses UW's Facilities Websites to get all Building Names"""

import re, json, os, requests
from bs4 import BeautifulSoup
from zlib import decompress
from pkgutil import get_data
import concurrent.futures as cf

dorm_site_re = re.compile(r'\([A-Z]{3,}\)\s?((\</div\>)|(\s?\| Campus Maps))')
dorm_abb_re = re.compile(r'\([A-Z]{3,}\)')

def seattle():
    """
    Parses UW's Facilities to get all Building Names for UW Seattle Campus

    Returns

        Dictionary with Building Name Abbreviations to full Building Names
    """
    def dorms():
        local_dorm_site_re = dorm_site_re
        local_dorm_abb_re = dorm_abb_re
        names = []
        # Get UW Seattle Dorm Building Abbreviations and Names
        uw_dorms = BeautifulSoup(requests.get('https://hfs.uw.edu/Live/Undergraduate-Residence-Halls-and-Apartments').text, 
                            features='lxml')
        for img in uw_dorms.find_all('img'):
            dorm_name = str(img).rsplit('alt="', 1)[-1].split('"', 1)[0]
            if 'Hall' in dorm_name:
                dorm_site = BeautifulSoup(requests.get(f'https://www.google.dz/search?q=http://www.washington.edu/maps UW {dorm_name}').text, 
                                        features='lxml').find('html')
                match = re.search(local_dorm_site_re, str(dorm_site))
                if match:
                    dorm_abb = re.search(local_dorm_abb_re, match.group(0)).group(0)[1:-1]
                    names.append((dorm_abb, dorm_name))
        return names

    def classrooms():
        # Get UW Seattle Classroom Building Abbreviations and Names
        names = []
        uw_classrooms = BeautifulSoup(requests.get('https://www.washington.edu/classroom/').text, 
                                    features='lxml')
        uw_classrooms = uw_classrooms.find("div", {"id": "buildings"})
        for link in uw_classrooms.find_all('a'):
            if link.get('href') not in buildings_dict:
                names.append((link.get('href'), link.text.rsplit('(', 1)[0].strip()))
        return names

    def buildings():
        # Supplement previous UW Building scrape with additional data
        names = []
        buildings = BeautifulSoup(requests.get('https://www.washington.edu/students/reg/buildings.html').text,
                                features='lxml')
        buildings = str(buildings.html).split('<h2>Code - Building Name (Map Grid)</h2>', 1)[-1]
        buildings = BeautifulSoup(buildings.rsplit('<div class="uw-footer">', 1)[0], features='lxml')
        for building_group in buildings.find_all('p'):
            for building in str(building_group).split('<br/>'):
                if 'a href' in building:
                    building = BeautifulSoup(building, features='lxml')
                    abbreviation = building.find('code').text
                    name = building.find('a').text
                    names.append((abbreviation, name.split('\n', 1)[0].strip()))
        return names
    
    buildings_dict = {}
    functions = [dorms, classrooms, buildings]
    with cf.ThreadPoolExecutor() as executor:
        results = [executor.submit(f) for f in functions]
        for f in cf.as_completed(results):
            for key, value in f.result():
                if key not in buildings_dict:
                    buildings_dict[key] = value
    return buildings_dict


def bothell():
    """
    Parses UW's Facilities to get all Building Names for UW Bothell Campus

    Returns

        Dictionary with Building Name Abbreviations to full Building Names
    """
    buildings = {}
    bothell_buildings = BeautifulSoup(requests.get('https://www.uwb.edu/safety/hours').text, features='lxml')
    for building in bothell_buildings.find_all('div', {'class': ['col1', 'col2', 'col3']}):
        bld = str(building.find('h3'))
        if bld and '(' in bld:
            abbreviation = bld.rsplit('(', 1)[-1].split(')')[0]
            name = bld.rsplit('(', 1)[0].strip()
            buildings[abbreviation] = name.replace('<h3>', '').replace('&amp;', '&')
    return buildings

building_re = re.compile(r'[A-Z]{2,} \d+')

def tacoma():
    """
    Parses UW's Facilities to get all Building Names for UW Seattle Campus

    Returns

        Dictionary with Building Name Abbreviations to full Building Names
    """
    buildings = {}
    local_building_re = building_re
    tacoma_buildings = BeautifulSoup(requests.get('https://www.tacoma.uw.edu/campus-map/buildings').text, 
                                features='lxml')
    for building in tacoma_buildings.find('div', class_='field-items').find('ul').find_all('a'):
        text = building.text
        if '(' in text:
            abbreviation = text.rsplit('(', 1)[-1].split(')')[0]
            name = text.rsplit('(', 1)[0].strip()
            buildings[abbreviation] = name
        else:
            link = BeautifulSoup(requests.get('https://www.tacoma.uw.edu{}'.format(str(building.get('href')))).text,
                                            features='lxml')
            for table in link.find_all('table'):
                for l in table.find_all('a'):
                    if re.search(local_building_re, l.text):
                        buildings[str(l.text).split(' ', 1)[0].upper()] = text
                        break
    return buildings


def get_buildings(campuses=['Seattle', 'Bothell', 'Tacoma']):
    """
    Returns the Buildings at UW for each campus

    @params

        'campuses': The Campuses to get the Buildings from

    Returns

        A dictionary with building name abbreviations to full names.
    """
    assert all([c in ['Seattle', 'Bothell', 'Tacoma'] for c in list(map(str.title, campuses))])
    buildings = {}
    functions = [seattle, bothell, tacoma]
    campuses = [function for function in functions if function.__name__.title() in campuses]
    with cf.ThreadPoolExecutor() as executor:
        results = [executor.submit(campus) for campus in campuses]
        for f in cf.as_completed(results):
            for key, value in f.result().items():
                buildings[key] = value
    return buildings


def geocode(buildings=[], campuses=['Seattle', 'Bothell', 'Tacoma']):
    """
    Geocodes UW Buildings

    @params

        'buildings': List of buildings to geocode

        'campuses': Campuses to get building coordinates from

    Returns

        A dictionary with data for each building in the buildings list
    """
    assert all([c in ['Seattle', 'Bothell', 'Tacoma'] for c in list(map(str.title, campuses))])
    coordinates = json.loads(decompress(get_data(__package__, 'Coordinates.file')))
    all_campuses = {}
    for campus, buildings_ in coordinates.items():
        if campus.title() in campuses:
            for building, coords in buildings_.items():
                if building in buildings or not buildings:
                    all_campuses[building] = coords
    return all_campuses
Parses UW's Facilities Websites to get all Building Names Parses UW's Facilities to get all Building Names for UW Seattle Campus      Returns          Dictionary with Building Name Abbreviations to full Building Names # Get UW Seattle Dorm Building Abbreviations and Names # Get UW Seattle Classroom Building Abbreviations and Names # Supplement previous UW Building scrape with additional data Parses UW's Facilities to get all Building Names for UW Bothell Campus      Returns          Dictionary with Building Name Abbreviations to full Building Names Parses UW's Facilities to get all Building Names for UW Seattle Campus      Returns          Dictionary with Building Name Abbreviations to full Building Names Returns the Buildings at UW for each campus      @params          'campuses': The Campuses to get the Buildings from      Returns          A dictionary with building name abbreviations to full names. Geocodes UW Buildings      @params          'buildings': List of buildings to geocode          'campuses': Campuses to get building coordinates from      Returns          A dictionary with data for each building in the buildings list