├── passyunk ├── __init__.py ├── pdata │ ├── __init__.py │ ├── .gitignore │ ├── apte.csv │ ├── directional.csv │ ├── apt.csv │ ├── update_version.py │ ├── saint.csv │ ├── suffix.csv │ ├── version.py │ ├── name_switch.csv │ ├── apt_std.csv │ └── alias_streets.csv ├── tests │ ├── cli_test.py │ ├── test.py │ ├── test_long_jfk.py │ ├── test_zip_zip4.py │ ├── test_rearrange_floor_tokens.py │ ├── test_floor_parsing.py │ └── test_lookups.py ├── landmark.py ├── data.py ├── namestd.py ├── rearrange_floor_tokens.py ├── centerline.py └── parser.py ├── .gitignore ├── pyproject.toml └── README.md /passyunk/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /passyunk/pdata/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /passyunk/pdata/.gitignore: -------------------------------------------------------------------------------- 1 | # data files 2 | *usps* 3 | *election* 4 | *zip* 5 | !usps_epf.py 6 | *.json 7 | src/ -------------------------------------------------------------------------------- /passyunk/tests/cli_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pprint import pprint 3 | from passyunk.parser import PassyunkParser 4 | 5 | p = PassyunkParser() 6 | try: 7 | a = sys.argv[1] 8 | except IndexError: 9 | print('No address specified') 10 | 11 | r = p.parse(a) 12 | pprint(r) 13 | -------------------------------------------------------------------------------- /passyunk/pdata/apte.csv: -------------------------------------------------------------------------------- 1 | APT REAR,APT REAR 2 | BSMT,BASEMENT 3 | BSMT,BSEMNT 4 | BSMT,BSMT 5 | FRNT,FRNT 6 | FRNT,FRONT 7 | LBBY,LBBY 8 | LBBY,LOBBY 9 | LOTS,LOTS 10 | OFC,OFC 11 | OFC,OFFICE 12 | PH,PH 13 | REAR,REAR 14 | REAR APT,REAR APT 15 | UPPR,UPPER 16 | APT A,APTA 17 | APT B,APTB 18 | APT C,APTC 19 | SIDE,SIDE 20 | LOWR,LOWR 21 | LOWR,LOWER 22 | UPPR,UPPR 23 | UPPR,UPPER 24 | -------------------------------------------------------------------------------- /passyunk/tests/test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | from passyunk.parser import PassyunkParser 4 | 5 | parser = PassyunkParser() 6 | 7 | parsed = parser.parse('253 PORT ROYAL') 8 | #parsed = parser.parse('PHILAREDEVELOPMENTAUTHORITYSOPHILLY') 9 | # print(parsed) 10 | 11 | print(json.dumps(parsed, sort_keys=True, indent=2)) 12 | 13 | ##53109644,09/09/2016 00:00:00,"ASSIGNMENT OF MORTGAGE","","SHELLPOINT MORTGAGE SERVICING","","",6311,"","","REGENT","ST","","" 14 | -------------------------------------------------------------------------------- /passyunk/pdata/directional.csv: -------------------------------------------------------------------------------- 1 | EAST,EAST,E 2 | EAST,E,E 3 | NORTH,NORTH,N 4 | NORTH,N,N 5 | NORTH,NO,N 6 | NORTH,NOTH,N 7 | NORTH EAST,NORTH EAST,NE 8 | NORTH WEST,NORTH WEST,NW 9 | NORTHEAST,NORTHEAST,NE 10 | NORTHEAST,NE,NE 11 | NORTHEAST,NE,NE 12 | NORTHWEST,NORTHWEST,NW 13 | NORTHWEST,NW,NW 14 | NORTHWEST,NW,NW 15 | SOUTH,SOUTH,S 16 | SOUTH,S,S 17 | SOUTH,SO,S 18 | SOUTH EAST,SOUTH EAST,SE 19 | SOUTH WEST,SOUTH WEST,SW 20 | SOUTHEAST,SOUTHEAST,SE 21 | SOUTHEAST,SE,SE 22 | SOUTHEAST,SE,SE 23 | SOUTHWEST,SOUTHWEST,SW 24 | SOUTHWEST,SW,SW 25 | SOUTHWEST,SW,SW 26 | WEST,WEST,W 27 | WEST,W,W 28 | -------------------------------------------------------------------------------- /passyunk/pdata/apt.csv: -------------------------------------------------------------------------------- 1 | APT,APART 2 | APT,APARTMENT 3 | APT,APARTMNT 4 | APT,APRTMNT 5 | APT,APT 6 | APT,APTMNT 7 | APT A,APTA 8 | APT B,APTB 9 | APT C,APTC 10 | BLDG,BLDG 11 | BLDG,BUILDING 12 | BSMT,BASEMENT 13 | BSMT,BSEMNT 14 | BSMT,BSMT 15 | BLK,BLK 16 | BLK,BLOCK 17 | DEPT,DEPT 18 | FL,FL 19 | FL,FLR 20 | FL,FLOOR 21 | FRNT,FRNT 22 | FRNT,FRONT 23 | LOT,LOT 24 | LOWR,LOWR 25 | LOWR,LOWER 26 | LBBY,LBBY 27 | LBBY,LOBBY 28 | OFC,OFC 29 | OFC,OFFICE 30 | PH,PH 31 | PH,PENTHOUSE 32 | PIER,PIER 33 | REAR,REAR 34 | REAR,R 35 | RM,RM 36 | SIDE,SIDE 37 | RM,ROOM 38 | SLIP,SLIP 39 | SPC,SPC 40 | STE,STE 41 | SUITE,SUITE 42 | STOP,STOP 43 | TRLR,TRLR 44 | UNIT,UNIT 45 | UNIT,UNITS 46 | UPPR,UPPR 47 | UPPR,UPPER 48 | -------------------------------------------------------------------------------- /passyunk/tests/test_long_jfk.py: -------------------------------------------------------------------------------- 1 | from passyunk.parser import PassyunkParser 2 | import pytest 3 | 4 | @pytest.fixture 5 | def p(): 6 | return PassyunkParser() 7 | 8 | def test_long_jfk(p): 9 | """Make sure that city/state/ZIP are junked appropriately and floor is at end as desired""" 10 | test_input = "1401 John F. Kennedy Blvd. 10th Floor Philadelphia, PA 19102" 11 | ans = p.parse(test_input) 12 | ac = ans["components"] 13 | print(ac) 14 | assert ac["output_address"] == "1401 JOHN F KENNEDY BLVD FL 10" 15 | assert ac["base_address"] == "1401 JOHN F KENNEDY BLVD" 16 | assert ac["street"]["full"] == "JOHN F KENNEDY BLVD" 17 | assert ac["street"]["name"] == "JOHN F KENNEDY" 18 | assert ac["street"]["suffix"] == "BLVD" 19 | assert ac["floor"]["floor_num"] == "10" 20 | assert ac["floor"]["floor_type"] == "FL" 21 | assert ac["mailing"]["zipcode"] == "19102" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | .DS_Store 57 | 58 | # pycharm 59 | .idea 60 | 61 | venv/ 62 | sandbox/ 63 | .vscode 64 | github_key* 65 | Notes.md 66 | *.doc 67 | *.docx 68 | *.zip 69 | *.tar 70 | *.json 71 | # Test -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Documentation on setuptools & pyproject.toml - https://setuptools.pypa.io/en/latest/userguide/index.html 2 | [build-system] 3 | requires = ["setuptools"] 4 | build-backend = "setuptools.build_meta" 5 | 6 | [project] 7 | name = "passyunk" 8 | version = "2.39.0" 9 | description = "Address parser for City of Philadelphia addresses" 10 | readme = "README.md" 11 | authors = [ 12 | {name = "Tom Swanson"}, 13 | {name = "James Midkiff", email = "james.midkiff@phila.gov"} 14 | ] 15 | requires-python = ">=3.7" 16 | dependencies = [ 17 | 'fuzzywuzzy>=0.11.1,<1.0', 18 | 'Levenshtein>=0.20,<1.0', 19 | 'requests>=2.28,<3.0', 20 | 'importlib-metadata>=6.0,<=7.0' 21 | ] 22 | 23 | [project.optional-dependencies] 24 | private = ["passyunk_automation @ git+ssh://git@github.com/CityOfPhiladelphia/passyunk_automation.git"] 25 | # The following syntax works: 26 | # pip install "passyunk[private] @ git+https://github.com/CityOfPhiladelphia/passyunk@" 27 | 28 | [tool.setuptools.packages.find] 29 | include = ["passyunk", "passyunk.pdata"] 30 | 31 | [tool.setuptools.package-data] 32 | "*" = ["*.csv"] 33 | -------------------------------------------------------------------------------- /passyunk/pdata/update_version.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import click 4 | # CityGeo 5 | from version import Version 6 | 7 | @click.command() 8 | @click.option('--file', help='File to update') 9 | @click.option('--get_version', '-g', 'path', flag_value='get_version', help='Get current version version of file, returning text') 10 | @click.option('--no_update', '-n', 'path', flag_value='no_update', help='Increment minor version of file without updating file, returning text') 11 | @click.option('--update', '-u', 'path', flag_value='update', help='Increment and update the minor version of file') 12 | @click.option('--version', '-v', help='Version to use updating, must be valid SemVer syntax') 13 | def main(file, path, version): 14 | ''' 15 | \b 16 | This module is not intended to be run by Passyunk users; it is used by CityGeo 17 | to update the module. Enclose filename in single-quotes if it contains whitespace. 18 | This module must be called with one of the flags "--get_version", "--no_update", "--update". 19 | ''' 20 | if path == None: 21 | raise ValueError('One of the flags "--get_version", "--no_update", "--update" must be provided') 22 | with open(file, 'r') as f: 23 | filedata = f.read() 24 | 25 | version_text = re.findall(f'(?<=version=\').*?(?=\')', filedata)[0] 26 | old_version = Version(version_text) 27 | if path == 'get_version': 28 | print(old_version.version) 29 | sys.exit(0) 30 | 31 | if path == 'no_update': 32 | new_version = old_version.increment_minor() 33 | print(new_version.version) 34 | sys.exit(0) 35 | 36 | if path == 'update': 37 | new_version = Version(version) 38 | filedata = filedata.replace( 39 | f"version='{old_version.version}'", f"version='{new_version.version}'") 40 | 41 | with open(file, 'w') as file: 42 | file.write(filedata) 43 | sys.exit(0) 44 | 45 | if __name__ == '__main__': 46 | main() -------------------------------------------------------------------------------- /passyunk/tests/test_zip_zip4.py: -------------------------------------------------------------------------------- 1 | from passyunk.parser import PassyunkParser 2 | import pytest 3 | 4 | @pytest.fixture 5 | def p(): 6 | return PassyunkParser() 7 | 8 | # check that passyunk_automation zip4 data is installed before running these tests 9 | @pytest.fixture 10 | def private_installed(): 11 | # Private Data 12 | try: 13 | from passyunk_automation.zip4 import create_zip4_lookup, get_zip_info # usps_zip4s.csv - Private 14 | from passyunk_automation.election import create_election_lookup, get_election_info # election_block.csv - Private 15 | return True 16 | except ModuleNotFoundError as e: 17 | return False 18 | 19 | 20 | def test_zip_1(p, private_installed): 21 | test_addr = "1 S BROAD ST FL 14" 22 | ac = p.parse(test_addr)['components'] 23 | assert private_installed 24 | assert ac['mailing']['zipcode'] == '19107' # should not be blank 25 | 26 | 27 | def test_zip_2(p, private_installed): 28 | test_addr = "761 S 4TH ST FL 3" 29 | ac = p.parse(test_addr)['components'] 30 | assert private_installed 31 | assert ac['mailing']['zipcode'] == '19147' # should not be blank 32 | 33 | 34 | def test_zip4_1(p, private_installed): 35 | test_addr = "1 COMCAST CTR FL 32" 36 | ac = p.parse(test_addr)['components'] 37 | assert private_installed 38 | assert ac['mailing']['zip4'] == '2855' #incorrect base: 2833 39 | 40 | def test_zip4_2(p, private_installed): 41 | test_addr = "1 S BROAD ST FL 11" 42 | ac = p.parse(test_addr)['components'] 43 | print(ac['mailing']['zip4']) 44 | assert private_installed 45 | assert ac['mailing']['zip4'] is None #incorrect base: 3426 46 | 47 | def test_zip4_unittype(p, private_installed): 48 | """Ensure that get_zip_info() returns the proper unit type when floor is NOT involved""" 49 | test_addr = "1130 SPRUCE ST # 1C" 50 | ac = p.parse(test_addr)['components'] 51 | assert private_installed 52 | assert ac['mailing']['zip4'] == '6004' 53 | assert ac['address_unit']['unit_num'] == '1C' 54 | assert ac['address_unit']['unit_type'] == 'APT' 55 | 56 | 57 | def test_unittype(p, private_installed): 58 | test_addr = "1 ACADEMY CIR # 112" 59 | ac = p.parse(test_addr)['components'] 60 | assert private_installed 61 | assert ac['address_unit']['unit_num'] == '112' 62 | assert ac['address_unit']['unit_type'] == 'UNIT' -------------------------------------------------------------------------------- /passyunk/tests/test_rearrange_floor_tokens.py: -------------------------------------------------------------------------------- 1 | from passyunk.rearrange_floor_tokens import rearrange_floor_tokens 2 | import pytest 3 | 4 | 5 | def test_basic1(): 6 | assert rearrange_floor_tokens(['MARKET', 'ST', '15F']) == ['MARKET', 'ST', 'FL', '15'] 7 | 8 | def test_basic2(): 9 | assert rearrange_floor_tokens(['MARKET', 'ST', '15FL']) == ['MARKET', 'ST', 'FL', '15'] 10 | 11 | def test_basic3(): 12 | assert rearrange_floor_tokens(['MARKET', 'ST', 'GROUND', 'FLOOR']) == ['MARKET', 'ST', 'FLOOR', 'GROUND'] 13 | 14 | def test_basic4(): 15 | assert rearrange_floor_tokens(['MARKET', 'ST', 'FLOOR', '15', 'OFFICE']) == ['MARKET', 'ST', 'OFFICE', 'FLOOR', '15'] 16 | 17 | def test_basic5(): 18 | assert rearrange_floor_tokens(['MARKET', 'ST', 'FLOOR', '15', 'APT']) == ['MARKET', 'ST', 'APT', 'FLOOR', '15'] 19 | 20 | def test_asterisks1(): 21 | assert rearrange_floor_tokens(['MARKET', 'ST', 'UNIT', '#', '6', 'FLOOR', '#', '15']) == ['MARKET', 'ST', 'UNIT', '#', '6', 'FLOOR', '15'] 22 | 23 | def test_asterisks2(): 24 | assert rearrange_floor_tokens(['MARKET', 'ST', 'FLOOR', '#', '15', 'UNIT', '#', '6']) == ['MARKET', 'ST', 'UNIT', '#', '6', 'FLOOR', '15'] 25 | 26 | def test_asterisks3(): 27 | assert rearrange_floor_tokens(['MARKET', 'ST', 'FL', '#', '15', 'UNIT', '6']) == ['MARKET', 'ST', 'UNIT', '6', 'FL', '15'] 28 | 29 | def test_lbby(): 30 | assert rearrange_floor_tokens(['MARKET', 'ST', 'GROUND', 'FLOOR', 'LBBY']) == ['MARKET', 'ST', 'LBBY', 'FLOOR', 'GROUND'] 31 | 32 | def test_rearrange_trash(): 33 | assert rearrange_floor_tokens(['1234', 'MARKET', 'ST', 'FLOOR', '7', 'JUNK', 'TRASH', 'GARBAGE']) == ['1234', 'MARKET', 'ST', 'JUNK', 'TRASH', 'GARBAGE', 'FLOOR', '7'] 34 | 35 | def test_rearrange_trash2(): 36 | assert rearrange_floor_tokens(['1234', 'MARKET', 'ST', '15F', 'TRASH', 'NONSENSE']) == ['1234', 'MARKET', 'ST', 'TRASH', 'NONSENSE', 'FL', '15'] 37 | 38 | # Things that should not be changed by this function 39 | 40 | def test_rearrange_apt_nf(): 41 | assert rearrange_floor_tokens(['1234', 'MARKET', 'ST', 'APT', '3F']) == ['1234', 'MARKET', 'ST', 'APT', '3F'] 42 | 43 | def test_rearrange_unit_nf(): 44 | assert rearrange_floor_tokens(['1234', 'MARKET', 'ST', 'UNIT', '3F']) == ['1234', 'MARKET', 'ST', 'UNIT', '3F'] 45 | 46 | def test_rearrange_pound_nf(): 47 | assert rearrange_floor_tokens(['1234', 'MARKET', 'ST', '#', '3F']) == ['1234', 'MARKET', 'ST', '#', '3F'] 48 | 49 | def test_rearrange_side_nf(): 50 | assert rearrange_floor_tokens('1009 S 8TH ST SIDE 2F'.split()) == '1009 S 8TH ST SIDE 2F'.split() 51 | 52 | def test_rearrange_streetnumber_ends_in_f(): 53 | assert rearrange_floor_tokens('3411F SPRING GARDEN ST'.split()) == '3411F SPRING GARDEN ST'.split() 54 | 55 | def test_lstrip_0_from_floornum(): 56 | assert rearrange_floor_tokens('3405 ARTHUR ST # 01ST FL'.split()) == ['3405', 'ARTHUR', 'ST', '#', 'FL', '1'] -------------------------------------------------------------------------------- /passyunk/landmark.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | import csv 3 | import re 4 | import string 5 | from fuzzywuzzy import process 6 | from .namestd import StandardName 7 | 8 | 9 | class Landmark: 10 | def __init__(self, item): 11 | self.item = item 12 | self.landmark_name = '' 13 | self.landmark_address = '' 14 | self.is_landmark = False 15 | 16 | def csv_path(self, file_name): 17 | cwd = os.path.dirname(__file__) 18 | cwd += '/pdata' 19 | return os.path.join(cwd, file_name + '.csv') 20 | 21 | def list_landmarks(self, first_letter): 22 | path = self.csv_path('landmarks') 23 | landmark_dict = {} 24 | try: 25 | with open(path, 'r', encoding='utf-8') as f: 26 | reader = csv.reader(f) 27 | for row in reader: 28 | # Don't match on 'the' as first word 29 | rlist = row[0].split() 30 | rlist = rlist[1:] if rlist[0] == 'THE' else rlist 31 | lname = ' '.join(rlist) 32 | if lname[0] != first_letter: 33 | continue 34 | if not lname in landmark_dict: 35 | landmark_dict[lname] = [] 36 | landmark_dict[lname].append(row[1]) 37 | except IOError: 38 | print('Error opening ' + path, sys.exc_info()[0]) 39 | return landmark_dict 40 | 41 | def landmark_check(self): 42 | tmp = self.item.strip() 43 | # Name standardization: 44 | tmp_list = re.sub('[' + string.punctuation + ']', '', tmp).split() 45 | std = StandardName(tmp_list, False).output 46 | # Don't match on 'the' if first word 47 | try: 48 | tmp = ' '.join(std[1:]) if std[0].upper() in ('THE', 'TEH') else ' '.join(std) 49 | except: 50 | tmp = tmp.upper() 51 | # Fuzzy matching: 52 | try: 53 | first_letter = tmp[0] 54 | except: 55 | first_letter = '' 56 | 57 | landmark_dict = self.list_landmarks(first_letter) 58 | landmark_list = [x for x in landmark_dict.keys()] 59 | results = process.extract(tmp, landmark_list, limit=3) 60 | results = sorted(results, key=lambda r: r[1], reverse=True) 61 | try: 62 | results = [] if results[0][1] == results[1][1] else results 63 | lname = results[0][0] 64 | landmark_addresses = landmark_dict[lname] 65 | # Currently only handle uniquely named landmarks 66 | # landmark_address = landmark_addresses[0] if results[0][1] > 89 and len(landmark_addresses) == 1 else '' 67 | landmark_address = landmark_addresses[0] if results[0][1] > 89 else '' 68 | self.is_landmark = True if landmark_address else False 69 | self.landmark_address = landmark_address 70 | self.landmark_name = lname 71 | except: 72 | pass 73 | 74 | -------------------------------------------------------------------------------- /passyunk/data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | __author__ = 'tom.swanson' 5 | 6 | # dict to standardize predirs/postdirs 7 | DIRS_STD = { 8 | 'N': 'N', 9 | 'NO': 'N', 10 | 'NORTH': 'N', 11 | 'S': 'S', 12 | 'SO': 'S', 13 | 'SOUTH': 'S', 14 | 'E': 'E', 15 | 'EAST': 'E', 16 | 'W': 'W', 17 | 'WEST': 'W' 18 | } 19 | 20 | STREET_NAME_STD_EXCEPTION = { 21 | 'ANDREW AVE': 'ANDREWS AVE', 22 | 'MEETING HOUSE DR': 'MEETINGHOUSE DR', 23 | 'ALLENS ST': 'ALLEN ST', 24 | 'N PHILIPS ST': 'N PHILIP ST', 25 | 'S PHILIPS ST': 'S PHILIP ST' 26 | 27 | } 28 | # dict to standardize suffixes 29 | SUFFIXES_STD = {} 30 | 31 | with open(os.path.join(os.path.dirname(__file__), 'pdata/suffix.csv')) as f: 32 | for line in f.readlines(): 33 | cols = line.split(',') 34 | common = cols[1] 35 | std = cols[2] 36 | SUFFIXES_STD[common] = std 37 | 38 | APTFLOOR = ['FL', 'FLR', 'FLOOR'] 39 | NON_NUMERIC_FLOORS = ['G', 'GROUND', 'L', 'LL', 'LOBBY', 'B', 'BSMT', 'PH'] 40 | CONJUNCTIONS = ['AND', '@', '\\', 'AT', '&'] 41 | STATELIST = ['PA', 'PENNSYLVANIA'] 42 | CITYLIST = ['PHILADELPHIA', 'PHILA', 'PHILLY', 'PHILADELPHA', 'PHILADELPHIA', 'PHILADELHIA', 'PHIALDELPHIA', 43 | 'PHILADLPHIA'] 44 | CARDINAL_DIR = ['N', 'E', 'S', 'W'] 45 | PREPOSTDIR = ['INDEPENDENCE MALL', 'SCHUYLKILL AVE', 'WASHINGTON LN'] 46 | POSTDIR = ['LOGAN CIR', 'PINE PL', 'ASHMEAD PL', 'MARWOOD RD'] 47 | PREDIR_AS_NAME = ['WEST END', 'EAST FALLS'] 48 | SUFFIX_IN_NAME = ['SPRING GRDN', 'AUTUMN HL', 'CHESTNUT HL', 'COBBS CRK', 'DELAIRE LNDG', 'HICKORY HL', 'FAIR HL', 49 | 'HUNTING PARK', 'AYRDALE CRES'] 50 | APTSPECIAL_2TOKEN = ['2ND FL', '1ST FL', '2ND', '1ST', 'PINE PL', 'SCHUYLKILL AVE'] 51 | APTSPECIAL_1TOKEN = ['2ND', '1ST', '2R', '1R', '01FL', '02FL', '03FL'] 52 | FLOOR_MAX = 99 53 | 54 | zipcode_re = re.compile('^(\d{5}(\-\d{4})?)?$') 55 | 56 | # 3 digits, N or S, 2 digits, - 4 digits or just 4 digits 57 | # 123N12-1234 or 123N121234 58 | mapreg_re = re.compile('^(\d{3}([N]|[S])(\d{2})(\d{4}|[-]\d{4}))?$') 59 | # mapreg_re = re.compile('^(\d{3}([N]|[S])(\d{2})(\-\d{4}|\d{4})?)?$') 60 | 61 | # 9 digit numeric 62 | opa_account_re = re.compile('^(\d{9})?$') 63 | 64 | po_box_re = re.compile('^P(\.|OST)? ?O(\.|FFICE)? ?BOX (?P\w+)$') 65 | 66 | # latlon_re = re.compile('^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$') 67 | 68 | # These are all the special characters that are allowed in input addresses. 69 | # A few chars have to be escaped for regex purposes: - ^ ] \ 70 | SPECIAL_CHARS_ALLOWED = r' \-\\\t/&@,.#' 71 | # Add alphanumerics to special chars allowed, negate, and compile regex object. 72 | ILLEGAL_CHARS_RE = re.compile('[^A-Z0-9{}]'.format(SPECIAL_CHARS_ALLOWED)) 73 | 74 | class Enum(set): 75 | def __getattr__(self, name): 76 | if name in self: 77 | return name 78 | raise AttributeError 79 | 80 | 81 | AddrType = Enum(['none', # Any entry not identified below 82 | 'address', # 1234 MARKET ST 83 | 'block', # 1200 block of Market St 84 | 'intersection_addr', # MARKET and 12TH ST 85 | 'coord', # Lat Lon coordinate 86 | 'mapreg', # 123N12-1234 87 | 'opa_account', # 123456789 88 | 'place', # CITY HALL 89 | 'pobox', # POBOX 1234 90 | # 'range', # 1200 - 1298 Market St - leaving placemarker in case we ever revert 91 | 'latlon', # Lat/Lon wgs84 92 | 'stateplane', # NAD_1983_StatePlane_Pennsylvania_South_FIPS_3702_Feet 93 | 'street', # MARKET ST 94 | 'zipcode', 95 | 'landmark']) # 19125 96 | -------------------------------------------------------------------------------- /passyunk/pdata/saint.csv: -------------------------------------------------------------------------------- 1 | ADAMNAN 2 | AGNES 3 | AIDEN 4 | ALBAN 5 | ALBANS 6 | ALBERT 7 | ALBIN 8 | ALBION 9 | ALEXANDER 10 | ALEXIS 11 | ALMOS 12 | ALPHONSUS 13 | AMAND 14 | AMANT 15 | AMBROSE 16 | ANDKE 17 | ANDRA 18 | ANDRE 19 | ANDRENS 20 | ANDREW 21 | ANDREWS 22 | ANGE 23 | ANGELA 24 | ANN 25 | ANNA 26 | ANNE 27 | ANSELMS 28 | ANTHON 29 | ANTHONY 30 | ANTOINE 31 | ANTON 32 | ARC 33 | ARMAND 34 | ARMANDS 35 | ARNOLD 36 | ARTHUR 37 | ASPAH 38 | AUBIN 39 | AUGUSTINE 40 | BARBARA 41 | BARBE 42 | BARNABAS 43 | BARTHELEMY 44 | BARTHOLOMEW 45 | BEDE 46 | BENARD 47 | BENEDICT 48 | BERNARD 49 | BONAVENTURE 50 | BONIFACE 51 | BOSWELL 52 | BRANDYWINE 53 | BRIDGET 54 | BRIDGETS 55 | BRIGID 56 | CABRINI 57 | CANVINETTE 58 | CATHERINE 59 | CECILIA 60 | CHALES 61 | CHAMPION 62 | CHARLES 63 | CHRISTOPHER 64 | CLAIN 65 | CLAIR 66 | CLAIRE 67 | CLARA 68 | CLARE 69 | CLARIA 70 | CLAUDE 71 | CLEMENTS 72 | CLERE 73 | CLOUD 74 | COLUMBA 75 | COLUMBUS 76 | CONIFER 77 | CROIX 78 | CULLINS 79 | DANIEL 80 | DAVID 81 | DECLAU 82 | DENIS 83 | DENNIS 84 | DEVON 85 | DOMINIC 86 | DUNSTANS 87 | EDMUND 88 | EDWARD 89 | EDWARDS 90 | EGBERT 91 | ELEANORAS 92 | ELENA 93 | ELIAS 94 | ELIZABETH 95 | ELIZEBETH 96 | ELLSWORTH 97 | ELMO 98 | ELMORE 99 | ELMOS 100 | EMANUEL 101 | EMMA 102 | ETIENNE 103 | EUNICE 104 | EVANS 105 | FELIX 106 | FERDINAND 107 | FEROL 108 | FINANS 109 | FLORIAN 110 | FLORINE 111 | FRANCES 112 | FRANCIS 113 | FRANCISVILLE 114 | FRANCOIS 115 | GABRIEL 116 | GALILEE 117 | GALL 118 | GANN 119 | GAUDENS 120 | GAUL 121 | GENEVA 122 | GEORGE 123 | GEORGES 124 | GERASIMOS 125 | GERMAIN 126 | GERMAINE 127 | GERTRUDE 128 | GERTRUDES 129 | GILES 130 | GODDARD 131 | GREGER 132 | GREGORY 133 | HAVEN 134 | HEBRON 135 | HEDWIG 136 | HELAIRE 137 | HELEN 138 | HELENA 139 | HENRY 140 | HILAIRE 141 | HILARIE 142 | HONORE 143 | HORACE 144 | HUBERT 145 | HUGH 146 | HYACINTH 147 | IGNACE 148 | IGNATHIUS 149 | IGNATIUS 150 | ILLIA 151 | ISABEL 152 | ISADORES 153 | IVAS 154 | IVES 155 | JACOB 156 | JACQUES 157 | JAMES 158 | JANE 159 | JEAN 160 | JEANNE 161 | JEFFREY 162 | JO 163 | JOACHIM 164 | JOCHIN 165 | JOE 166 | JOESEPH 167 | JOHANN 168 | JOHN 169 | JOHNSBURY 170 | JOHNSVILLE 171 | JONE 172 | JORDAN 173 | JOSE 174 | JOSEPH 175 | JUDE 176 | JUDES 177 | JULIEN 178 | KATHERINE 179 | KATHERYN 180 | KATHRYNS 181 | KEVIN 182 | KILDA 183 | KILLIAN 184 | KILLIANS 185 | KITTS 186 | LANDRY 187 | LAURENCE 188 | LAURENT 189 | LAWRENCE 190 | LEA 191 | LEO 192 | LEON 193 | LEONARD 194 | LORENT 195 | LOTUS 196 | LOUIS 197 | LUCAS 198 | LUCIA 199 | LUCIE 200 | LUCILLE 201 | LUCY 202 | LUDMILA 203 | LUKE 204 | LUPEE 205 | MALACHI 206 | MARCO 207 | MARGARET 208 | MARIA 209 | MARIE 210 | MARK 211 | MARLO 212 | MARON 213 | MARTHA 214 | MARTHAS 215 | MARTIN 216 | MARVE 217 | MARY 218 | MATHEW 219 | MATTHEW 220 | MAURICE 221 | MEMORIAL 222 | MERRYN 223 | MICHAEL 224 | MICHEL 225 | MICHELLE 226 | MIGUEL 227 | MIHIEL 228 | MINDEL 229 | MONICA 230 | MORITZ 231 | MORTIZ 232 | MORTZ 233 | NEAL 234 | NICHOLAS 235 | NICHOLIS 236 | NICK 237 | NICOLAS 238 | NORBERT 239 | OLAF 240 | ONGE 241 | PALOMA 242 | PARIS 243 | PATRICK 244 | PATS 245 | PAUL 246 | PERDUE 247 | PETE 248 | PETER 249 | PETERSBURG 250 | PHILIP 251 | PHILLIP 252 | PIERCE 253 | PIERRE 254 | PIUSDO 255 | RAYMOND 256 | REGIS 257 | REMY 258 | RENE 259 | RICHARDS 260 | RIGHARD 261 | RITA 262 | RITTS 263 | ROBERT 264 | ROCH 265 | ROGERS 266 | ROMAIN 267 | ROSE 268 | SAMUELS 269 | SAUVEUR 270 | SERAPHIM 271 | SIBLEY 272 | SIMON 273 | SIMONS 274 | SMITH 275 | SOUKUP 276 | SPRING 277 | STANISLAUS 278 | STEPHEN 279 | SUSAN 280 | SYLVESTER 281 | TAMMANY 282 | TERESA 283 | THERESA 284 | THOMAS 285 | TIMOTHY 286 | TROPEZ 287 | VAUGHAN 288 | VIANNY 289 | VICTORS 290 | VINCENT 291 | VITH 292 | VRAIN 293 | WENDEL 294 | WILHOIT 295 | WILLIAM 296 | WILLIAMS 297 | WITHAM 298 | XAVIER 299 | MALACHY 300 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # passyunk 2 | 3 | Address parser and standardizer for the City of Philadelphia 4 | 5 | ## Installation 6 | ``` 7 | pip install git+https://github.com/CityOfPhiladelphia/passyunk 8 | ``` 9 | If you have been granted access to the private data files, you can install the `passyunk_automation` package at the same time by running 10 | ``` 11 | pip install "passyunk[private] @ git+https://github.com/CityOfPhiladelphia/passyunk" 12 | ``` 13 | 14 | To find where passyunk is installed, from the command line run 15 | ``` 16 | python 17 | import passyunk 18 | passyunk.__file__ 19 | ``` 20 | ## Usage 21 | 22 | ### Quickstart 23 | 24 | from passyunk.parser import PassyunkParser 25 | p = PassyunkParser() 26 | parsed = p.parse('1234 MARKET ST') 27 | standardized_address = parsed['components']['output_address'] 28 | 29 | ### Parser.parse(address) 30 | 31 | Takes an address, standardizes it, and returns a dictionary of address components. 32 | 33 | { 34 | "components": { 35 | "address": { 36 | "addr_suffix": null, 37 | "addrnum_type": "N", 38 | "fractional": null, 39 | "full": "1234", 40 | "high": null, 41 | "high_num": null, 42 | "high_num_full": null, 43 | "isaddr": true, 44 | "low": "1234", 45 | "low_num": 1234, 46 | "parity": "E" 47 | }, 48 | "address_unit": { 49 | "unit_num": null, 50 | "unit_type": null 51 | }, 52 | "base_address": "1234 MARKET ST", 53 | "cl_addr_match": "A", 54 | "cl_responsibility": "STATE", 55 | "cl_seg_id": "440394", 56 | "election": { 57 | "blockid": "24021362", 58 | "precinct": "0528" 59 | }, 60 | "floor": { 61 | "floor_num": null, 62 | "floor_type": null 63 | }, 64 | "mailing": { 65 | "bldgfirm": "MARKET STREET BLDG", 66 | "matchdesc": "Multiple Zip4 Matches", 67 | "uspstype": "H", 68 | "zip4": "3721", 69 | "zipcode": "19107" 70 | }, 71 | "output_address": "1234 MARKET ST", 72 | "street": { 73 | "full": "MARKET ST", 74 | "is_centerline_match": true, 75 | "name": "MARKET", 76 | "parse_method": "CL1", 77 | "postdir": null, 78 | "predir": null, 79 | "score": null, 80 | "street_code": "53560", 81 | "suffix": "ST" 82 | }, 83 | "street_2": { 84 | "full": null, 85 | "is_centerline_match": false, 86 | "name": null, 87 | "parse_method": null, 88 | "postdir": null, 89 | "predir": null, 90 | "score": null, 91 | "street_code": null, 92 | "suffix": null 93 | } 94 | }, 95 | "input_address": "1234 market street", 96 | "type": "address" 97 | } 98 | 99 | ## Data Updates 100 | The data in the folder folder `passyunk/pdata` is public; the files `centerline.csv` and `centerline_streets.csv` are refreshed on a continual basis, and each data update will create a new version tag for this repository in the format '1.x.0'. 101 | 102 | The private data is housed in the separate `passyunk_automation` package, where files are updated on a monthly basis. Each private data update will result in a new private version tag for this repository in the format '1.y.0+private'. 103 | 104 | Thus the version 1.4.0 refers solely to the 4th public data update while the version 1.4.0+private refers solely to the 4th private data update. No public data will be updated in a new private data version nor vice-versa, so 1.4.0 and 1.4.0+private have no connection to each other, breaking a rule of SemVer syntax. However, 1.4.0 does contain updated public data compared to 1.3.0, and 1.4.0+private does contain updated private data compared to 1.3.0+private. 105 | 106 | When `passyunk` is first imported into a python script, it will warn the user if the module's public data version is less than the latest public data version tag on GitHub. Similarly, if it detects that `passyunk_automation` has been installed, it will perform the same check for the private data version. The module will still work fine even if the data is out-of-date. 107 | -------------------------------------------------------------------------------- /passyunk/namestd.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | import csv 3 | from .parser_addr import namestd_lookup 4 | 5 | class StandardName: 6 | def __init__(self, tokens, do_ordinal): 7 | # print(' '.join(self.name_std(tokens, False))) 8 | # self.tokens = tokens 9 | self.output = self.name_std(tokens, do_ordinal) 10 | 11 | 12 | class Namestd: 13 | def __init__(self, row): 14 | self.correct = row[0] 15 | self.common = row[1] 16 | 17 | 18 | class AddrOrdinal: 19 | def __init__(self, row): 20 | self.ordigit = row[0] 21 | self.orsuffix = row[1] 22 | 23 | 24 | def csv_path(self, file_name): 25 | cwd = os.path.dirname(__file__) 26 | cwd += '/pdata' 27 | return os.path.join(cwd, file_name + '.csv') 28 | 29 | # This is run on parser initialization and imported above as namestd_lookup 30 | def create_namestd_lookup(self): 31 | path = self.csv_path('std') 32 | with open(path, 'r') as f: 33 | lookup = {} 34 | try: 35 | reader = csv.reader(f) 36 | for row in reader: 37 | r = self.Namestd(row) 38 | lookup[r.common] = r 39 | except IOError: 40 | print('Error opening ' + path, sys.exc_info()[0]) 41 | return lookup 42 | 43 | 44 | def is_name_std(self, test): 45 | try: 46 | nstd = namestd_lookup[test] 47 | except KeyError: 48 | row = ['', test] 49 | nstd = self.Namestd(row) 50 | # print("nstd: ", nstd.common) 51 | return nstd 52 | 53 | 54 | def create_ordinal_lookup(self): 55 | lookup = {} 56 | r = self.AddrOrdinal(['1', 'ST']) 57 | lookup[r.ordigit] = r 58 | r = self.AddrOrdinal(['11', 'TH']) 59 | lookup[r.ordigit] = r 60 | r = self.AddrOrdinal(['2', 'ND']) 61 | lookup[r.ordigit] = r 62 | r = self.AddrOrdinal(['12', 'TH']) 63 | lookup[r.ordigit] = r 64 | r = self.AddrOrdinal(['3', 'RD']) 65 | lookup[r.ordigit] = r 66 | r = self.AddrOrdinal(['13', 'TH']) 67 | lookup[r.ordigit] = r 68 | r = self.AddrOrdinal(['4', 'TH']) 69 | lookup[r.ordigit] = r 70 | r = self.AddrOrdinal(['5', 'TH']) 71 | lookup[r.ordigit] = r 72 | r = self.AddrOrdinal(['6', 'TH']) 73 | lookup[r.ordigit] = r 74 | r = self.AddrOrdinal(['7', 'TH']) 75 | lookup[r.ordigit] = r 76 | r = self.AddrOrdinal(['8', 'TH']) 77 | lookup[r.ordigit] = r 78 | r = self.AddrOrdinal(['9', 'TH']) 79 | lookup[r.ordigit] = r 80 | r = self.AddrOrdinal(['0', 'TH']) 81 | lookup[r.ordigit] = r 82 | 83 | return lookup 84 | 85 | 86 | def add_ordinal(self, string): 87 | if string == '0' or string == '00' or string == '000': 88 | return string 89 | 90 | if len(string[0]) > 1: 91 | lastchar = string[0][-2:] 92 | try: 93 | ordinal = self.add_ordinal_lookup[lastchar] 94 | if len(string) > 1 and ordinal.orsuffix == string[1]: 95 | string.pop() 96 | string[0] = string[0] + ordinal.orsuffix 97 | return string 98 | except Exception: 99 | pass 100 | 101 | lastchar = string[0][-1:] 102 | 103 | try: 104 | ordinal = self.add_ordinal_lookup[lastchar] 105 | string[0] = string[0] + ordinal.orsuffix 106 | return string 107 | except Exception: 108 | pass 109 | 110 | return string 111 | 112 | 113 | def name_std(self, tokens, do_ordinal): 114 | i = len(tokens) 115 | while i > 0: 116 | j = 0 117 | while j + i <= len(tokens): 118 | nstd = self.is_name_std(' '.join(tokens[j:j + i])) 119 | if nstd.correct != '': 120 | tokens[j] = nstd.correct 121 | k = j + 1 122 | while k < j + i: 123 | tokens[k] = '' 124 | k += 1 125 | j += 1 126 | i -= 1 127 | temp = " ".join(tokens).split() 128 | if do_ordinal and len(temp) > 0 and temp[0].isdigit(): 129 | temp = self.add_ordinal(temp) 130 | temp = self.name_std(temp, True) 131 | 132 | return temp 133 | 134 | # test = ['1234', 'MKT', 'ST'] 135 | # std = StandardName(test, False).output 136 | # print(std) -------------------------------------------------------------------------------- /passyunk/pdata/suffix.csv: -------------------------------------------------------------------------------- 1 | ALLEY,AL,ALY,0, 2 | ALLEY,ALLEE,ALY,0, 3 | ALLEY,ALLEY,ALY,0, 4 | ALLEY,ALLY,ALY,0, 5 | ALLEY,ALY,ALY,1, 6 | AVENUE,AV,AVE,0, 7 | AVENUE,AVE,AVE,1, 8 | AVENUE,AVEN,AVE,0, 9 | AVENUE,AVENE,AVE,0, 10 | AVENUE,AVENU,AVE,0, 11 | AVENUE,AVENEU,AVE,0, 12 | AVENUE,AVENUE,AVE,0, 13 | AVENUE,AVN,AVE,0, 14 | AVENUE,AVNUE,AVE,0, 15 | AVENUE,AVEUE,AVE,0, 16 | AVENUE,VE,AVE,0, 17 | AVENUE,SVE,AVE,0, 18 | AVENUE,AZE,AVE,0,$ 19 | BOULEVARD,BLV,BLVD,0,$ 20 | BOULEVARD,BLVD,BLVD,1, 21 | BOULEVARD,BLVD,BLVD,1, 22 | BOULEVARD,BOUL,BLVD,0, 23 | BOULEVARD,BVLD,BLVD,0, 24 | BOULEVARD,BVD,BLVD,0, 25 | BOULEVARD,BOULEVARD,BLVD,0, 26 | BOULEVARD,BOULV,BLVD,0, 27 | BOULEVARD,BLOULEVARD,BLVD,0, 28 | CENTER,CEN,CTR,0, 29 | CENTER,CENT,CTR,0, 30 | CENTER,CENTER,CTR,0, 31 | CENTERS,CENTERS,CTRS,0, 32 | CENTER,CENTR,CTR,0, 33 | CENTER,CENTRE,CTR,0, 34 | CIRCLE,CIR,CIR,1, 35 | CIRCLE,CIRC,CIR,0, 36 | CIRCLE,CIRCL,CIR,0, 37 | CIRCLE,CIRCLE,CIR,0, 38 | CIRCLES,CIRCLES,CIRS,0, 39 | CIRCLES,CIRS,CIRS,1, 40 | CREEK,CK,CRK,0, 41 | CREEK,CR,CRK,0, 42 | CIRCLE,CRCL,CIR,0, 43 | CIRCLE,CRCLE,CIR,0, 44 | CRESCENT,CRECENT,CRES,0, 45 | CREEK,CREEK,CRK,0, 46 | CRESCENT,CRES,CRES,1, 47 | CRESCENT,CRESCENT,CRES,0, 48 | CRESCENT,CRESENT,CRES,0, 49 | CREEK,CRK,CRK,1, 50 | CRESCENT,CRSENT,CRES,0, 51 | COBBS CREEK,COBBSCREEK,0, 52 | COBBS CREEK,COBBCREEK,0, 53 | COBBS,COBB,0, 54 | COURT,CRT,CT,0, 55 | COURT,CT,CT,1, 56 | COURT,COURT,CT,, 57 | CENTER,CTR,CTR,1, 58 | DRIVEWAY,DWY,DWY,1, 59 | DRIVE,DDR,DR,0, 60 | DRIVE,DR,DR,1, 61 | DRIVE,DRIV,DR,0, 62 | DRIVE,DRIVE,DR,0, 63 | DRIVE,DRV,DR,0, 64 | DRIVE,DRW,DR,0,$ 65 | DIVIDE,DV,DV,1, 66 | DIVIDE,DVD,DV,0, 67 | EXPRESSWAY,EXP,EXPY,0, 68 | EXPRESSWAY,EXPR,EXPY,0, 69 | EXPRESSWAY,EXPRESS,EXPY,0, 70 | EXPRESSWAY,EXPRESSWAY,EXPY,0, 71 | EXPRESSWAY,EXPW,EXPY,0, 72 | EXPRESSWAY,EXPY,EXPY,1, 73 | EXTENSION,EXT,EXT,1, 74 | EXTENSION,EXTENSION,EXT,0, 75 | EXTENSIONS,EXTENSIONS,EXTS,0, 76 | EXTENSION,EXTN,EXT,0, 77 | EXTENSION,EXTNSN,EXT,0, 78 | EXTENSIONS,EXTS,EXTS,1, 79 | EXPRESSWAY,EXWY,EXPY,0, 80 | HEIGHTS,HEIGHT,HTS,0, 81 | HEIGHTS,HEIGHTS,HTS,0, 82 | HEIGHTS,HGTS,HTS,0, 83 | HEIGHTS,HTS,HTS,1, 84 | HIGHWAY,HGWY,HWY,0, 85 | HIGHWAY,HIGHWAY,HWY,0, 86 | HIGHWAY,HIGHWY,HWY,0, 87 | HILL,HILL,HL,0, 88 | HIGHWAY,HIWAY,HWY,0, 89 | HIGHWAY,HIWY,HWY,0, 90 | HILL,HL,HL,1, 91 | HIGHWAY,HWAY,HWY,0, 92 | HIGHWAY,HWY,HWY,1, 93 | LANE,LA,LN,0, 94 | LANDING,LANDING,LNDG,0, 95 | LANE,LANE,LN,0, 96 | LANE,LANES,LN,0, 97 | LANE,LLN,LN,0,$ 98 | LANE,LN,LN,1, 99 | LANE,LNLN,LN,0,$ 100 | MALL,MAL,MALL,0,$ 101 | MALL,MALL,MALL,1, 102 | MALL,ML,MALL,0, 103 | MEWS,MEW,MEWS,0, 104 | MEWS,MEWS,MEWS,1, 105 | PARK,PARK,PARK,1, 106 | PARKS,PARKS,PARK,0, 107 | PARKS,PRK,PARK,0, 108 | PARKWAY,PARKWAY,PKWY,0, 109 | PARKWAYS,PARKWAYS,PKWY,0, 110 | PARKWAY,PARKWY,PKWY,0, 111 | PATH,PATH,PATH,1, 112 | PATH,PATHS,PATH,0, 113 | PIKE,PIKE,PIKE,1, 114 | PIKE,PIKES,PIKE,0, 115 | PIKE,PK,PIKE,0, 116 | PIKE,PKE,PIKE,0, 117 | PARKWAY,PKWAY,PKWY,0, 118 | PARKWAY,PKWY,PKWY,1, 119 | PARKWAYS,PKWYS,PKWY,0, 120 | PARKWAY,PKY,PKWY,0, 121 | PLACE,PL,PL,1, 122 | PLACE,PLACE,PL,0, 123 | PLACE,PLC,PL,0, 124 | PLAZA,PLZ,PLZ,1, 125 | PLAZA,PLAZA,PLZ,0, 126 | PARKWAY,PWY,PKWY,0, 127 | PARKWAY,PKW,PKWY,0, 128 | RAMP,RAMP,RAMP,1, 129 | ROAD,RD,RD,1, 130 | ROAD,ROAD,RD,0, 131 | ROW,ROW,ROW,1, 132 | RUN,RUN,RUN,1, 133 | SPUR,SPUR,SPUR,1, 134 | SPURS,SPURS,SPUR,0, 135 | SQUARE,SQ,SQ,1, 136 | SQUARE,SQR,SQ,0, 137 | SQUARE,SQRE,SQ,0, 138 | SQUARE,SQU,SQ,0, 139 | SQUARE,SQUARE,SQ,0, 140 | STREET,ST,ST,1, 141 | STREET,SRTEET,ST,0, 142 | STREET,STREE,ST,0, 143 | STREET,STR,ST,0, 144 | STREET,STRET,ST,0, 145 | STREET,STREET,ST,, 146 | STREET,STREER,ST,0, 147 | STREET,STREEET,ST,0, 148 | STREET,STEEET,ST,0, 149 | STREET,STTEET,ST,0, 150 | STREET,STEET,ST,0, 151 | STREET,STREETS,ST,0, 152 | STREET,STREEY,ST,0, 153 | STREET,ATREET,ST,0, 154 | STREET,STRERT,ST,0, 155 | STREET,STRRT,ST,0, 156 | STREET,THSTREET,ST,0, 157 | STREET,STREETS,ST,0, 158 | STREET,ZT,ST,0, 159 | STREET,STT,ST,0, 160 | STREET,STST,ST,0, 161 | STREET,SY,ST,0, 162 | TERRACE,TER,TER,1, 163 | TERRACE,TERR,TER,0, 164 | TERRACE,TR,TER,0, 165 | TERRACE,TERRACE,TER,0, 166 | TURNPIKE,TNPK,TPKE,0,$ 167 | TURNPIKE,TPK,TPKE,0, 168 | TURNPIKE,TPKE,TPKE,1, 169 | TRAIL,TRAIL,TRL,0, 170 | TRAIL,TRAILS,TRL,0, 171 | TRAIL,TRL,TRL,1, 172 | TRAIL,TRLS,TRL,0, 173 | TURNPIKE,TRNPK,TPKE,0, 174 | TURNPIKE,TRPK,TPKE,0, 175 | TUNNEL,TUNL,TUNL,1, 176 | TUNNEL,TUNLS,TUNL,0, 177 | TUNNEL,TUNNEL,TUNL,0, 178 | TUNNEL,TUNNELS,TUNL,0, 179 | TUNNEL,TUNNL,TUNL,0, 180 | TURNPIKE,TURN,TPKE,0, 181 | TURNPIKE,TURNPIKE,TPKE,0, 182 | TURNPIKE,TURNPK,TPKE,0, 183 | VILLAGE,VILL,VLG,0, 184 | VILLAGE,VILLAG,VLG,0, 185 | VILLAGE,VILLAGE,VLG,0, 186 | VILLAGE,VILLG,VLG,0, 187 | VILLAGE,VILLIAGE,VLG,0, 188 | VILLAGE,VLG,VLG,1, 189 | VALLEY,VLLY,VLY,0, 190 | VALLEY,VLY,VLY,1, 191 | WALK,WALK,WALK,1, 192 | WALKS,WALKS,WALK,0, 193 | WAY,WAY,WAY,1, 194 | WALKS,WK,WALK,0, 195 | WALKS,WLK,WALK,0, 196 | WAY,WY,WAY,0, 197 | -------------------------------------------------------------------------------- /passyunk/pdata/version.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Union, List 3 | Array = List[str] 4 | 5 | class Version: 6 | ''' 7 | A class to hold attributes and methods for a software version that follows 8 | Symantic Versioning (SemVer) syntax. See https://semver.org/ for syntax details. 9 | Class variable SEMVER holds the RegEx used to capture and validate a version. 10 | 11 | Includes support for the following comparison operators: <, >, <=, >=, ==, != 12 | ''' 13 | SEMVER = '^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' 14 | 15 | def __init__(self, version=None): 16 | if version == None: 17 | self.version = None 18 | self.major = None 19 | self.minor = None 20 | self.patch = None 21 | self.prerelease = None 22 | self.buildmetadata= None 23 | else: 24 | m = self.check(version, return_match=True) 25 | self.version = version 26 | self.major = int(m['major']) 27 | self.minor = int(m['minor']) 28 | self.patch = int(m['patch']) 29 | self.prerelease = m['prerelease'] 30 | self.buildmetadata= m['buildmetadata'] 31 | 32 | def check(self, version: str, return_match: bool) -> re.match: 33 | ''' 34 | Raise a ValueError if a version does not use valid SemVer syntax, otherwise 35 | return a re.match object that splits the fields. 36 | ''' 37 | m = re.match(self.SEMVER, version) 38 | if m == None: 39 | raise ValueError(f'Version "{version}" does not match Semantic Version schema - see https://semver.org/') 40 | if return_match: 41 | return m 42 | 43 | def create(self, 44 | major: Union[int, str], minor: Union[int, str], patch: Union[int, str], 45 | prerelease: str, buildmetadata: str) -> 'Version': 46 | ''' 47 | Create a Version from components 48 | ''' 49 | if prerelease == None: 50 | prerelease = '' 51 | else: 52 | prerelease = '-' + prerelease 53 | if buildmetadata == None: 54 | buildmetadata = '' 55 | else: 56 | buildmetadata = '+' + buildmetadata 57 | temp = (str(major) + '.' + str(minor) + '.' + str(patch) + prerelease + 58 | buildmetadata) 59 | self.check(temp, return_match=False) 60 | return Version(temp) 61 | 62 | def increment_minor(self) -> str: 63 | ''' 64 | Increment the minor version by one and return a new Version, resetting 65 | patch, prerelease, and buildmetadata. 66 | ''' 67 | return self.create(self.major, self.minor + 1, 0, None, None) 68 | 69 | def compare(self, other_version: 'Version') -> str: 70 | ''' 71 | Compare the major, minor, and patch between two Versions and return "lesser", 72 | "greater", or "equal". Does not compare prerelease or buildmetadata. 73 | ''' 74 | assert type(other_version) == Version, f'Other version must be an object of the Version class, not {type(other_version)}' 75 | 76 | if self.major < other_version.major: 77 | return 'lesser' 78 | if self.major > other_version.major: 79 | return 'greater' 80 | if self.minor < other_version.minor: 81 | return 'lesser' 82 | if self.minor > other_version.minor: 83 | return 'greater' 84 | if self.patch < other_version.patch: 85 | return 'lesser' 86 | if self.patch > other_version.patch: 87 | return 'greater' 88 | else: 89 | return 'equal' 90 | 91 | def __lt__(self, other_version: 'Version'): 92 | return self.compare(other_version) == 'lesser' 93 | 94 | def __gt__(self, other_version: 'Version'): 95 | return self.compare(other_version) == 'greater' 96 | 97 | def __le__(self, other_version: 'Version'): 98 | rv = self.compare(other_version) 99 | return (rv == 'lesser' or rv == 'equal') 100 | 101 | def __ge__(self, other_version: 'Version'): 102 | rv = self.compare(other_version) 103 | return (rv == 'greater' or rv == 'equal') 104 | 105 | def __eq__(self, other_version: 'Version'): 106 | return self.compare(other_version) == 'equal' 107 | 108 | def __ne__(self, other_version: 'Version'): 109 | return self.compare(other_version) != 'equal' 110 | 111 | def __repr__(self): 112 | return self.version 113 | 114 | def find_newest(array: Array) -> 'Version': 115 | ''' 116 | Return the newest Version out of an array of elements coercible to Versions, 117 | ignoring prerelease and buildmetadata 118 | ''' 119 | current_max = Version('0.0.0') 120 | for v in array: 121 | v = Version(v) 122 | if v > current_max: 123 | current_max = v 124 | return current_max -------------------------------------------------------------------------------- /passyunk/rearrange_floor_tokens.py: -------------------------------------------------------------------------------- 1 | #from data import APTFLOOR, NON_NUMERIC_FLOORS # for standalone testing of file, uncomment this and comment line below 2 | from .data import APTFLOOR, NON_NUMERIC_FLOORS 3 | import re 4 | 5 | def is_floor_num(token: str) -> bool: 6 | """Check whether this token can come AFTER a word for floor.""" 7 | return (token.isdigit() or 8 | token in NON_NUMERIC_FLOORS or 9 | (token[0] == '#' and token[1:].isdigit())) 10 | 11 | ORD_RE = r'(\d)(ST|ND|RD|TH)' # will match string endings such as 1ST, 11TH, 2ND, 3RD, 4TH... 12 | 13 | def is_floor_ordinal(token: str) -> bool: 14 | """Check whether this token can come BEFORE a word for floor.""" 15 | return (len(token) >= 3 and 16 | (re.search(ORD_RE, token) or 17 | token in NON_NUMERIC_FLOORS)) 18 | 19 | 20 | def remove_ordinal_suffix(token: str) -> str: 21 | return re.sub(ORD_RE, r'\1', token).lstrip('0') 22 | 23 | 24 | # TODO: consider replacing with a csv-derived lookup object in line with create_aptstd_lookup 25 | # TODO: consider eliminating this option altogether since a lot of legit addresses are e.g. '...APT 2F' or 'UNIT 2F' 26 | def is_oneword_floor(token: str) -> bool: 27 | if len(token) > 4: # prevent legit unit numbers like #18261F from becoming floors 28 | return False 29 | return ((token[:-1].isdigit() and token[-1] == 'F') or 30 | (token[:-2].isdigit() and token[-2:] == 'FL')) 31 | 32 | def rearrange_floor_tokens(tokens: list[str]) -> list[str]: 33 | """Put the portion of a tokens list representing the floor at the end, so it 34 | can be easily dealt with in handle_units() prior to preexisting passyunk logic""" 35 | if len(tokens) < 3: 36 | return tokens 37 | 38 | if tokens[-1] in APTFLOOR: # e.g. [... "GROUND", "FLOOR"] 39 | if is_floor_ordinal(tokens[-2]): 40 | tokens[-2] = remove_ordinal_suffix(tokens[-2]) 41 | moving_token = tokens.pop(-1) 42 | tokens.insert(-1, moving_token) 43 | return tokens 44 | 45 | if tokens[-2] in APTFLOOR: 46 | if is_floor_num(tokens[-1]): # e.g. [..."FLOOR", "15"] 47 | return tokens 48 | if is_floor_ordinal(tokens[-3]): # e.g. [..."GROUND", "FLOOR", "OFFICE"] 49 | tokens[-3] = remove_ordinal_suffix(tokens[-3]) 50 | moving_token = tokens.pop(-1) 51 | tokens.insert(-2, moving_token) # i.e. insert third-to-last 52 | floor_token = tokens.pop(-1) 53 | tokens.insert(-1, floor_token) 54 | return tokens 55 | 56 | if is_oneword_floor(tokens[-1]): # e.g. [... "15F"] 57 | if tokens[-2] in ['APT', 'UNIT', '#', 'FRNT', 'SIDE', 'STE']: # but not e.g. [... 'UNIT', '1F'] 58 | return tokens 59 | moving_token = tokens.pop(-1) 60 | moving_token = re.sub(r'F|L|#', '', moving_token) 61 | tokens.append("FL") 62 | tokens.append(moving_token) 63 | return tokens 64 | 65 | if tokens[-3] in APTFLOOR: # e.g. [...'FLOOR', '#', '7'] 66 | if tokens[-2] == '#' and is_floor_num(tokens[-1]): 67 | tokens.pop(-2) 68 | return tokens 69 | 70 | # Walk back through the tokens, looking for a floor designator earlier in the input 71 | for ix, token in enumerate(tokens[::-1]): 72 | nix = -ix - 1 # get the pythonic negative index 73 | if nix >= -2: 74 | continue 75 | if nix == -len(tokens): # street numbers like '3411F SPRING GARDEN ST' should remain unchanged 76 | break 77 | if token in APTFLOOR: 78 | if is_floor_num(tokens[nix+1]): # e.g. [...'FLOOR', '7', ...] 79 | moving_slice = tokens[nix:nix+2] 80 | before_tokens = tokens[:nix] 81 | after_tokens = tokens[nix+2:] 82 | tokens = before_tokens + after_tokens + moving_slice 83 | # tokens[-1] = no_numbersign(tokens[-1]) 84 | return tokens 85 | if tokens[nix+1] == '#' and tokens[nix+2].isdigit(): # e.g. [...'FLOOR', '#', '7', ...] 86 | moving_slice = tokens[nix:nix+3] 87 | before_tokens = tokens[:nix] 88 | after_tokens = tokens[nix+3:] 89 | tokens = before_tokens + after_tokens + moving_slice 90 | tokens.pop(-2) 91 | return tokens 92 | if is_floor_ordinal(tokens[nix-1]): # e.g. [...'GROUND', 'FLOOR', ...] or [... '15TH', 'FLOOR', ...] 93 | ordinal = tokens[nix-1] 94 | fl = tokens[nix] 95 | before_tokens = tokens[:nix-1] 96 | after_tokens = tokens[nix+1:] 97 | tokens = before_tokens + after_tokens 98 | tokens.append(fl) 99 | tokens.append(remove_ordinal_suffix(ordinal)) 100 | return tokens 101 | if is_oneword_floor(token): # e.g. [... '15F' ...] 102 | moving_token = tokens.pop(nix) 103 | moving_token = moving_token.replace('L', '').replace('F', '') 104 | tokens.append("FL") 105 | tokens.append(moving_token) 106 | return tokens 107 | 108 | return tokens -------------------------------------------------------------------------------- /passyunk/pdata/name_switch.csv: -------------------------------------------------------------------------------- 1 | Pre,Name,Suffix,Post,USPS 2 | ,54TH,DR,,S 54TH DR 3 | ,55TH,DR,,S 55TH DR 4 | W,64TH,AVE,,64TH AVE 5 | W,65TH,AVE,,65TH AVE 6 | ,ABERDEEN,ST,,N ABERDEEN ST 7 | ,AIKENS,ST,,S AIKENS ST 8 | ,APOLLO,PLZ,,APOLLO PL 9 | ,APSLEY,ST,,W APSLEY ST 10 | ,ARCOLA,ST,,S ARCOLA ST 11 | ,ARDELL,ST,,S ARDELL ST 12 | ,ARLAN,AVE,,ARLAN ST 13 | ,ARMSTRONG,ST,,E ARMSTRONG ST 14 | ,ASHFORD,ST,,S ASHFORD ST 15 | ,ASHLEY,RD,,ASHLEY ST 16 | N,ASHMEAD,PL,,ASHMEAD PL N 17 | S,ASHMEAD,PL,,ASHMEAD PL S 18 | ,AVNER,LN,,AVNER ST 19 | ,BANK,ST,,S BANK ST 20 | ,BARNER,PL,,BARNER DR 21 | ,BARRINGER,ST,,E BARRINGER ST 22 | ,BEACH,ST,,N BEACH ST 23 | ,BEAUMONT,ST,,BEAUMONT AVE 24 | ,BELLFORD,ST,,S BELLFORD ST 25 | ,BELLMORE,ST,,BELLMORE AVE 26 | ,BENEZET,ST,,E BENEZET ST 27 | ,BENTON,AVE,,BENTON ST 28 | ,BERBRO,ST,,S BERBRO ST 29 | ,BERKLEY,ST,,W BERKLEY ST 30 | ,BIALY,ST,,S BIALY ST 31 | ,BREAD,ST,,N BREAD ST 32 | ,BRINTON,ST,,E BRINTON ST 33 | ,BROOKLYN,ST,,N BROOKLYN ST 34 | ,BROUGHTON,RD,,BROUGHTON ST 35 | ,BUDD,ST,,N BUDD ST 36 | ,BURNS,ST,,N BURNS ST 37 | ,BUSTI,ST,,N BUSTI ST 38 | ,CADWALLADER,ST,,N CADWALLADER ST 39 | ,CARDEZA,ST,,E CARDEZA ST 40 | ,CARROLL,ST,,S CARROLL ST 41 | W,CHAMPLOST,AVE,,CHAMPLOST AVE 42 | ,CHANG,ST,,N CHANG ST 43 | ,CHIPPENDALE,AVE,,CHIPPENDALE ST 44 | N,CHRISTOPHER COLUMBUS,BLVD,,N COLUMBUS BLVD 45 | S,CHRISTOPHER COLUMBUS,BLVD,,S COLUMBUS BLVD 46 | ,CLAREMONT,RD,,E CLAREMONT RD 47 | ,CLARENDON,AVE,,CLARENDEN RD 48 | ,CLARISSA,ST,,N CLARISSA ST 49 | ,CLAYMONT,ST,,S CLAYMONT ST 50 | ,COLLOM,ST,,E COLLOM ST 51 | ,COSGROVE,ST,,E COSGROVE ST 52 | ,DALLAS,ST,,DALLAS RD 53 | ,DARIEN,WAY,,N DARIEN WAY 54 | ,DEARBORN,ST,,N DEARBORN ST 55 | ,DILWORTH,ST,,S DILWORTH ST 56 | ,DIVINITY,ST,,S DIVINITY ST 57 | ,DORRANCE,ST,,S DORRANCE ST 58 | ,DORSET,ST,,E DORSET ST 59 | ,DUNTON,ST,,E DUNTON ST 60 | ,DURAND,ST,,W DURAND ST 61 | ,DURARD,ST,,E DURARD ST 62 | ,EARLHAM,TER,,W EARLHAM TER 63 | ,EASTVIEW,RD,,EASTVIEW ST 64 | ,ELGIN,AVE,,ELGIN ST 65 | ,ELM,AVE,,S ELM ST 66 | ,ELWOOD,ST,,E ELWOOD ST 67 | E,ESTAUGH,ST,,ESTAUGH ST 68 | W,ESTAUGH,ST,,ESTAUGH ST 69 | ,EVERETT,AVE,,EVERETT ST 70 | ,FARISTON,DR,,E FARISTON DR 71 | ,FARRAGUT,ST,,S FARRAGUT ST 72 | ,FARSON,ST,,N FARSON ST 73 | W,FISHERS,LN,,W FISHER LN 74 | ,FLAGSHIP,DR,,FLAGSHIP AVE 75 | ,GARRETT,ST,,E GARRETT ST 76 | ,GIFFORD,AVE,,GIFFORD ST 77 | ,GLENCOE,AVE,,GLENCOE ST 78 | ,GLENFIELD,ST,,GLENFIELD RD 79 | ,GOULD,ST,,S GOULD ST 80 | ,GRAY,ST,,E GRAY ST 81 | ,GREENWOOD,ST,,GREENWOOD AVE 82 | ,GREYLOCK,ST,,S GREYLOCK ST 83 | ,GROVE,ST,,S GROVE ST 84 | ,HANSBERRY,ST,,W HANSBERRY ST 85 | ,HANSON,ST,,S HANSON ST 86 | ,HARSHAW,ST,,S HARSHAW ST 87 | ,HARVEY,ST,,W HARVEY ST 88 | ,HERMAN,ST,,E HERMAN ST 89 | ,HIGH,ST,,E HIGH ST 90 | ,HOBSON,ST,,S HOBSON ST 91 | ,HOLBROOK,ST,,S HOLBROOK ST 92 | ,HOPE,ST,,N HOPE ST 93 | ,HOWELL,ST,,E HOWELL ST 94 | ,HUTCHINSON,PL,,N HUTCHINSON PL 95 | ,HUTTON,ST,,N HUTTON ST 96 | ,JAMESTOWN,AVE,,JAMESTOWN ST 97 | ,JEFFERSON,ST,,W JEFFERSON ST 98 | W,JEROME,ST,,JEROME ST 99 | ,JUDSON,ST,,N JUDSON ST 100 | ,KESWICK,CIR,,S KESWICK CIR 101 | ,KESWICK,PLZ,,S KESWICK PLZ 102 | ,KESWICK,TER,,S KESWICK TER 103 | ,KING,ST,,W KING ST 104 | ,KISMET,TER,,KISMET PL 105 | ,LARRY,ST,,S LARRY ST 106 | ,LAUREL,ST,,W LAUREL ST 107 | ,LAUREL,ST,,E LAUREL ST 108 | ,LETITIA,ST,,S LETITIA ST 109 | ,LEX,ST,,N LEX ST 110 | ,LILAC,DR,,LILAC LN 111 | S,LLOYD,CT,,LLOYD CT 112 | ,LLOYD,ST,,S LLOYD ST 113 | ,LOCUST,AVE,,E LOCUST AVE 114 | ,LOTT,AVE,,LOTT ST 115 | ,LOUISE,ST,,LOUISE RD 116 | ,MANCHESTER,ST,,MANCHESTER AVE 117 | ,MANHEIM,ST,,W MANHEIM ST 118 | ,MAPLEWOOD,AVE,,W MAPLEWOOD AVE 119 | ,MAPLEWOOD,MALL,,W MAPLEWOOD MALL 120 | ,MARWOOD,RD,,MARWOOD RD E 121 | ,MASCHER,ST,,N MASCHER ST 122 | ,MAY,PL,,N MAY PL 123 | ,MCPHERSON,ST,,E MCPHERSON ST 124 | ,MEEHAN,AVE,,E MEEHAN AVE 125 | ,MILAN,ST,,S MILAN ST 126 | ,MILNE,ST,,W MILNE ST 127 | ,MONTANA,ST,,E MONTANA ST 128 | ,MOSS,ST,,N MOSS ST 129 | ,MOUNT PLEASANT,PL,,N MOUNT PLEASANT PL 130 | ,MUHFELD,ST,,S MUHLFELD ST 131 | ,NATHANIEL,PL,,NATHANIEL DR 132 | E,NEVADA,ST,,NEVADA ST 133 | ,NIPPON,ST,,W NIPPON ST 134 | ,OREGON,AVE,,E OREGON AVE 135 | ,OREGON,AVE,,W OREGON AVE 136 | ,PALETHORP,ST,,N PALETHORP ST 137 | ,PALLAS,ST,,N PALLAS ST 138 | ,PALM,ST,,N PALM ST 139 | ,PARK,LN,,W PARK LN 140 | ,PARK TOWNE,PL,,PARK TOWNE PL W 141 | ,PARK TOWNE,PL,,PARK TOWNE PL S 142 | ,PARKSIDE,AVE,,PARKSIDE AVE N 143 | E,PELLE,CIR,,PELLE CIR E 144 | W,PELLE,CIR,,PELLE CIR W 145 | ,PENNOCK,ST,,N PENNOCK ST 146 | ,PICKERING,AVE,,PICKERING ST 147 | E,PLEASANT,PL,,PLEASANT PL 148 | ,PLEASANT,ST,,E PLEASANT ST 149 | ,PORTER,ST,,W PORTER ST 150 | ,PORTER,ST,,E PORTER ST 151 | ,PROVIDENT,ST,,PROVIDENT RD 152 | ,QUINCE,ST,,S QUINCE ST 153 | ,RAMSEY,ST,,N RAMSEY ST 154 | ,REGER,ST,,W REGER ST 155 | ,RESERVE,DR,,S RESERVE DR 156 | ,RESERVE,DR,,W RESERVE DR 157 | ,RETTA,ST,,RETTA AVE 158 | ,REXFORD,RD,,REXFORD ST 159 | ,RITNER,ST,,W RITNER ST 160 | ,RITTENHOUSE,SQ,,RITTENHOUSE SQUARE ST 161 | ,ROSELLA,PL,,S ROSELLA PL 162 | ,ROSELLA,ST,,S ROSELLA ST 163 | ,ROUMFORT,RD,,E ROUMFORT RD 164 | ,SALAIGNAC,ST,,W SALAIGNAC ST 165 | ,SALAIGNAC,ST,,E SALAIGNAC ST 166 | ,SANDYFORD,AVE,,SANDYFORD RD 167 | ,SANGER,ST,,E SANGER ST 168 | ,SAUNDERS,AVE,,N SAUNDERS AVE 169 | ,SEDGELEY,DR,,W SEDGLEY DR 170 | ,SEMINOLE,AVE,,SEMINOLE ST 171 | ,SEYBERT,ST,,W SEYBERT ST 172 | ,SHEDAKER,ST,,E SHEDAKER ST 173 | ,SHEFFIELD,AVE,,SHEFFIELD ST 174 | ,SHIELDS,ST,,S SHIELDS ST 175 | ,SHUNK,ST,,W SHUNK ST 176 | ,SLOAN,ST,,N SLOAN ST 177 | ,SLOCUM,ST,,E SLOCUM ST 178 | S,SPANGLER,ST,,SPANGLER ST 179 | E,SPENCER,AVE,,E SPENCER ST 180 | W,SPENCER,AVE,,W SPENCER ST 181 | ,STATE,ST,,N STATE ST 182 | N,STATION,LN,,STATION LN 183 | ,STETLER,ST,,S STETLER ST 184 | ,STEVENSON,ST,,STEVENSON LN 185 | ,STRAWBERRY,ST,,S STRAWBERRY ST 186 | ,SYDNEY,ST,,E SYDNEY ST 187 | ,SYLMAR,ST,,S SYLMAR ST 188 | ,TANAGER,ST,,TANAGER PL 189 | N,LECOUNT,ST,,N TANEY ST 190 | S,LECOUNT,ST,,S TANEY ST 191 | W,TELNER,ST,,TELNER ST 192 | ,TOWANDA,AVE,,TOWANDA ST 193 | ,TUSTIN,AVE,,TUSTIN ST 194 | ,ULENA,ST,,S ULENA ST 195 | ,UNION,ST,,N UNION ST 196 | ,VADER,DR,,VADER RD 197 | ,VERNON,RD,,E VERNON RD 198 | W,VICTORIA,ST,,VICTORIA ST 199 | ,WADSWORTH,AVE,,E WADSWORTH AVE 200 | ,WARFIELD,ST,,S WARFIELD ST 201 | S,WASHINGTON,SQ,,WASHINGTON SQ S 202 | E,WELLENS,ST,,E WELLENS AVE 203 | W,WELLENS,ST,,W WELLENS AVE 204 | ,WESTVIEW,AVE,,WESTVIEW ST 205 | ,WETHERILL,WAY,,WETHERILL CT 206 | ,WILLINGTON,ST,,N WILLINGTON ST 207 | ,WINDSOR,ST,,WINDSOR AVE 208 | ,WIOTA,ST,,N WIOTA ST 209 | ,WOODALE,AVE,,WOODALE RD 210 | ,WYNEVA,ST,,E WYNEVA ST 211 | ,WYNEVA,ST,,W WYNEVA ST 212 | ,ZERALDA,ST,,W ZERALDA ST 213 | -------------------------------------------------------------------------------- /passyunk/tests/test_floor_parsing.py: -------------------------------------------------------------------------------- 1 | from passyunk.parser import PassyunkParser 2 | import pytest 3 | 4 | @pytest.fixture 5 | def p(): 6 | return PassyunkParser() 7 | 8 | 9 | @pytest.fixture 10 | def mkt(): 11 | return "1234 MARKET ST " 12 | 13 | 14 | def test_nonsense(p, mkt): 15 | test_addr = mkt + "Zebra Octopus Llama Fish Elephant Bird" 16 | ans = p.parse(test_addr) 17 | ac = ans['components'] 18 | assert ac['floor']['floor_num'] is None 19 | assert ac['floor']['floor_type'] is None 20 | assert ac['address_unit']['unit_num'] is None 21 | assert ac['address_unit']['unit_type'] is None 22 | 23 | 24 | def test_just_floor15(p, mkt): 25 | examples = [ 26 | "Fl 15", 27 | "Fl.15", 28 | "Fl#15", 29 | "Fl.#15", 30 | "Flr 15", 31 | "Floor 15", 32 | "15th Floor", 33 | # "Floor Fifteen", # doesn't work with number words yet 34 | ] 35 | for test_addr in examples: 36 | ans = p.parse(mkt + test_addr) 37 | ac = ans['components'] 38 | assert ac['floor']['floor_num'] == '15' 39 | assert ac['floor']['floor_type'] == 'FL' 40 | assert ac['address_unit']['unit_num'] == '15' 41 | assert ac['address_unit']['unit_type'] == 'FL' 42 | 43 | 44 | def test_floor15_unit6(p, mkt): 45 | examples = [ 46 | "Floor 15 Unit 6", 47 | "Unit 6 Floor 15", 48 | "Floor #15 Unit 6", 49 | "Unit 6 Floor #15", 50 | "Floor 15 Unit #6", 51 | "Unit #6 Floor 15", 52 | # "Floor #15 Unit #6", 53 | # "Unit 6 Floor #15", 54 | ] 55 | for test_addr in examples: 56 | ans = p.parse(mkt + test_addr) 57 | ac = ans['components'] 58 | assert ac['floor']['floor_num'] == '15' 59 | assert ac['floor']['floor_type'] == 'FL' 60 | assert ac['address_unit']['unit_num'] == '6' 61 | assert ac['address_unit']['unit_type'] == 'UNIT' 62 | 63 | 64 | def test_floor15_apt6(p, mkt): 65 | examples = [ 66 | "Floor 15 Apt 6", 67 | "Apt 6 Floor 15", 68 | ] 69 | for test_addr in examples: 70 | ans = p.parse(mkt + test_addr) 71 | ac = ans['components'] 72 | assert ac['floor']['floor_num'] == '15' 73 | assert ac['floor']['floor_type'] == 'FL' 74 | assert ac['address_unit']['unit_num'] == '6' 75 | assert ac['address_unit']['unit_type'] == 'APT' 76 | 77 | 78 | def test_floor15_office(p, mkt): 79 | examples = [ 80 | "Floor 15 Office", 81 | "Office Floor 15", 82 | "FL 15 OFC", 83 | "OFC FL 15" 84 | ] 85 | for test_addr in examples: 86 | ans = p.parse(mkt + test_addr) 87 | ac = ans['components'] 88 | assert ac['floor']['floor_num'] == '15' 89 | assert ac['floor']['floor_type'] == 'FL' 90 | assert ac['address_unit']['unit_num'] is None 91 | assert ac['address_unit']['unit_type'] == 'OFC' 92 | 93 | 94 | def test_15f(p, mkt): 95 | test_addr = mkt + "15F" 96 | ans = p.parse(test_addr) 97 | ac = ans['components'] 98 | assert ac['floor']['floor_num'] == '15' 99 | assert ac['floor']['floor_type'] == 'FL' 100 | assert ac['address_unit']['unit_num'] == '15' 101 | assert ac['address_unit']['unit_type'] == 'FL' 102 | 103 | 104 | def test_floor_word(p, mkt): 105 | examples = [ 106 | "Ground Floor", 107 | "Floor Ground" 108 | ] 109 | for test_addr in examples: 110 | ans = p.parse(mkt + test_addr) 111 | ac = ans['components'] 112 | assert ac['floor']['floor_num'] == 'GROUND' 113 | assert ac['floor']['floor_type'] == 'FL' 114 | assert ac['address_unit']['unit_num'] == 'GROUND' 115 | assert ac['address_unit']['unit_type'] == 'FL' 116 | 117 | def test_standalone_number(p, mkt): 118 | # This should not parse as a floor 119 | test_addr = mkt + "15" 120 | ans = p.parse(test_addr) 121 | ac = ans['components'] 122 | assert ac['floor']['floor_num'] is None 123 | assert ac['floor']['floor_type'] is None 124 | 125 | def test_adversarially_long_input(p, mkt): 126 | test_addr = mkt + "FL 99999999999999999" 127 | ans = p.parse(test_addr) 128 | ac = ans['components'] 129 | assert ac['floor']['floor_num'] is None 130 | assert ac['floor']['floor_type'] is None 131 | assert ac['address_unit']['unit_num'] is None 132 | assert ac['address_unit']['unit_type'] is None 133 | 134 | def test_11th_floor(p, mkt): 135 | test_addr = mkt + "11TH FL" 136 | ans = p.parse(test_addr) 137 | ac = ans['components'] 138 | assert ac['floor']['floor_num'] == '11' 139 | assert ac['floor']['floor_type'] == 'FL' 140 | assert ac['address_unit']['unit_num'] == '11' 141 | assert ac['address_unit']['unit_type'] == 'FL' 142 | 143 | def test_floor_lstrip_0(p, mkt): 144 | test_addr = mkt + "01ST FL" 145 | ans = p.parse(test_addr) 146 | ac = ans['components'] 147 | assert ac['floor']['floor_num'] == '1' 148 | assert ac['floor']['floor_type'] == 'FL' 149 | assert ac['address_unit']['unit_num'] == '1' 150 | assert ac['address_unit']['unit_type'] == 'FL' 151 | 152 | def test_apt_nf(p): 153 | test_addr = '1326 S BROAD ST APT 1F' 154 | ans = p.parse(test_addr) 155 | ac = ans['components'] 156 | assert ac['floor']['floor_num'] is None 157 | assert ac['floor']['floor_type'] is None 158 | assert ac['address_unit']['unit_num'] == '1F' 159 | assert ac['address_unit']['unit_type'] == 'APT' 160 | 161 | def test_apt_number_ends_in_f(p): 162 | test_addr = '3411F SPRING GARDEN ST' 163 | ans = p.parse(test_addr) 164 | ac = ans['components'] 165 | assert ac['floor']['floor_num'] is None 166 | assert ac['floor']['floor_type'] is None 167 | 168 | def test_18261F_edgecase(p): 169 | test_addr = '1826 GREEN ST # 18261F' 170 | ans = p.parse(test_addr) 171 | ac = ans['components'] 172 | assert ac['floor']['floor_num'] is None 173 | assert ac['floor']['floor_type'] is None 174 | assert ac['address_unit']['unit_num'] == '18261F' 175 | assert ac['address_unit']['unit_type'] == '#' 176 | 177 | def test_populate_floor_field_from_old_way(p): 178 | test_addr = '1517 ARROTT ST # 2ND' # this parsed to '1517 ARROTT ST FL 2' already using apt_std lookup or something like it 179 | ans = p.parse(test_addr) 180 | ac = ans['components'] 181 | # make sure that unit field values get copied back to floor field 182 | assert ac['floor']['floor_num'] == ac['address_unit']['unit_num'] == '2' 183 | assert ac['floor']['floor_type'] == ac['address_unit']['unit_type'] == 'FL' 184 | 185 | def test_ste_1f(p): 186 | test_addr = '1810 Spruce St Ste 1f' 187 | ans = p.parse(test_addr) 188 | ac = ans['components'] 189 | assert ac['floor']['floor_num'] is None 190 | assert ac['floor']['floor_type'] is None 191 | assert ac['address_unit']['unit_num'] == '1F' 192 | assert ac['address_unit']['unit_type'] == 'STE' -------------------------------------------------------------------------------- /passyunk/pdata/apt_std.csv: -------------------------------------------------------------------------------- 1 | 0-1,1 2 | 004D,12 3 | 004E,12 4 | 010A,10A 5 | 010B,10B 6 | 1-102,1-102 7 | 1-103,1-103 8 | 011B,11B 9 | 011G,11G 10 | 012A,12A 11 | 012B,12B 12 | 012C,12C 13 | 012D,12D 14 | 1-303,1-303 15 | 013A,13A 16 | 013B,13B 17 | 013F,13F 18 | 014A,14A 19 | 014B,14B 20 | 016A,16A 21 | 1-A,1A 22 | 01A2,1A2 23 | 001B,1B 24 | 01B1,1B1 25 | 001C,1C 26 | 01-C,1C 27 | 1-C,1C 28 | 001D,1D 29 | 1-D,1D 30 | 001E,1E 31 | 1-E,1E 32 | 001F,1F 33 | 01FF,1FF 34 | 1-F,1FF 35 | 001G,1G 36 | 001H,1H 37 | 01K3,1K3 38 | 001L,1L 39 | 001M,1M 40 | 001N,1N 41 | 001R,1R 42 | 01R,1R 43 | 1-R,1R 44 | 01RR,1RR 45 | 001S,1S 46 | 1S,1S 47 | 01ST,1ST 48 | 1ST-,1ST 49 | 001W,1W 50 | 2FL B,2 REAR 51 | 2FL BACK,2 REAR 52 | 026B,26B 53 | 002A,2A 54 | 002B,2B 55 | 02B,2B 56 | 2-B,2B 57 | 2 C,2C 58 | 002D,2D 59 | 002E,2E 60 | 02F,2F 61 | 2 F,2F 62 | 02FE,2FE 63 | 02FF,2FF 64 | 02FR,2FR 65 | 002G,2G 66 | 002H,2H 67 | 002J,2J 68 | 002L,2L 69 | 002M,2M 70 | 002N,2N 71 | 02ND,2ND 72 | 2ND,2ND 73 | 2ND-,2ND 74 | 002P,2P 75 | 002R,2R 76 | 02R,2R 77 | 2-R,2R 78 | 02RR,2RR 79 | 2-RR,2RR 80 | 002W,2W 81 | 032A,32A 82 | 033A,33A 83 | 033B,33B 84 | 039A,39A 85 | 003A,3A 86 | 03A3,3A3 87 | 003B,3B 88 | 03B,3B 89 | 03B3,3B3 90 | 003D,3D 91 | 003E,3E 92 | 003F,3F 93 | 03-F,3F 94 | 03FF,3FF 95 | 3FN,3FN 96 | 03FR,3FR 97 | 003G,3G 98 | 003H,3H 99 | 003J,3J 100 | 003K,3K 101 | 003M,3M 102 | 003N,3N 103 | 03NE,3NE 104 | 003R,3R 105 | 3-R,3R 106 | 03R,3RD 107 | 03RD,3RD 108 | 03RR,3RR 109 | 003S,3S 110 | 003W,3W 111 | 041A,41A 112 | 004A,4A 113 | 004B,4B 114 | 004C,4C 115 | 004F,4F 116 | 004G,4G 117 | 004H,4H 118 | 004J,4J 119 | 004N,4N 120 | 004R,4R 121 | 04RR,4RR 122 | 04TH,4TH 123 | 004W,4W 124 | 005A,5A 125 | 005B,5B 126 | 005C,5C 127 | 005E,5E 128 | 005F,5F 129 | 005I,5I 130 | 005N,5N 131 | 006A,6A 132 | 006B,6B 133 | 006C,6C 134 | 06TH,6TH 135 | 072A,72A 136 | 007A,7A 137 | 007B,7B 138 | 007E,7E 139 | 007F,7F 140 | 008A,8A 141 | 008B,8B 142 | 008C,8C 143 | 008D,8D 144 | 008E,8E 145 | 008F,8F 146 | 009A,9A 147 | 009B,9B 148 | 009C,9C 149 | 009F,9F 150 | 009H,9H 151 | 09NW,9NW 152 | 000A,A 153 | 00A,A 154 | 1FL A,A 155 | A000,A 156 | 001A,A1 157 | 00A1,A1 158 | A001,A1 159 | A01,A1 160 | A-01,A1 161 | A-1,A1 162 | 0A10,A10 163 | 0A12,A12 164 | 00A2,A2 165 | A002,A2 166 | A-02,A2 167 | 00A3,A3 168 | A003,A3 169 | 00A4,A4 170 | 00A5,A5 171 | 00A6,A6 172 | 00A7,A7 173 | 00A8,A8 174 | A-08,A8 175 | 00A9,A9 176 | 0AA8,AA8 177 | 00AB,AB 178 | AB,AB 179 | ABC,ABC 180 | AD,AD 181 | AEAST,AEAST 182 | AG,AG 183 | APARTMENT,APT 184 | APT,APT 185 | AR,AR 186 | AUP,AUP 187 | 000B,B 188 | 000G,G 189 | 2ND B,B 190 | 00B1,B1 191 | B001,B1 192 | B01,B1 193 | B-01,B1 194 | B-1,B1 195 | 0B10,B10 196 | B 10,B10 197 | B-10,B10 198 | 0B12,B12 199 | 0B14,B14 200 | B 15,B15 201 | B015,B15 202 | 0B18,B18 203 | 00B2,B2 204 | B002,B2 205 | B-02,B2 206 | 00B3,B3 207 | B 3,B3 208 | B03,B3 209 | 0B31,B31 210 | 00B4,B4 211 | B-04,B4 212 | 00B5,B5 213 | 00B6,B6 214 | 00B7,B7 215 | B007,B7 216 | 00B8,B8 217 | B 8,B8 218 | 00B9,B9 219 | BACK,BACK 220 | BASDEV,BASDEV 221 | BASMT,BASMT 222 | BB,BB 223 | BB6,BB 6 224 | BB9,BB 9 225 | B C,BC 226 | BC,BC 227 | BD,BD 228 | BDU,BDU 229 | BG,BG 230 | BASEMEN,BSMT 231 | BASEMENT,BSMT 232 | BSM,BSMT 233 | BSMNT,BSMT 234 | BSMT,BSMT 235 | BSMT C,BSMT C 236 | BUP,BUP 237 | 000C,C 238 | 00C1,C1 239 | 0C11,C11 240 | 0C13,C13 241 | C-14A,C-14A 242 | 00C2,C2 243 | 00C3,C3 244 | 00C4,C4 245 | 00C5,C5 246 | 00C6,C6 247 | 00C7,C7 248 | 00C8,C8 249 | 00C9,C9 250 | CA,CA 251 | CB,CB 252 | CBA,CBA 253 | CD,CD 254 | CE,CE 255 | CELL,CELL 256 | CH,CH 257 | CI,CI 258 | CLW,CLW 259 | COM,COM 260 | COMM,COMM 261 | COMMERC,COMMERC 262 | COMMON,COMMON 263 | CON,CON 264 | COTTAGE,COTTAGE 265 | CYA,CYA 266 | CYB,CYB 267 | 000D,D 268 | 00D1,D1 269 | 00D2,D2 270 | 00D3,D3 271 | 00D4,D4 272 | 00D6,D6 273 | 00D8,D8 274 | DOOR,DOOR 275 | DU,DU 276 | DUP,DUP 277 | 000E,E 278 | EF,EF 279 | ELW,ELW 280 | ENTRANCE,ENTRANCE 281 | 000F,F 282 | 1FL F,F 283 | 2ND F,F 284 | 00F1,F1 285 | 00F3,F3 286 | 00F4,F4 287 | FG,FG 288 | FH,FH 289 | 3FLR,FL 290 | 2FL2,FL 2 291 | 01FL,FL 1 292 | 01FR,FL 1 293 | 01ST FL,FL 1 294 | 01ST FL R,FL 1 295 | 01ST FLR,FL 1 296 | 1 F,FL 1 297 | 1 FL,FL 1 298 | 1 FLR,FL 1 299 | 1FL1,FL 1 300 | 1FLF,FL 1 301 | 1FLOOR,FL 1 302 | 1FLR,FL 1 303 | 1ST FLR,FL 1 304 | 1ST FL,FL 1 305 | 1ST FL 1,FL 1 306 | 1ST FL A,FL 1 307 | 1ST FL APT,FL 1 308 | 1ST FL F,FL 1 309 | 1ST FLOOR,FL 1 310 | 1ST FLR,FL 1 311 | 1ST FR,FL 1 312 | 1STF,FL 1 313 | 1STFL,FL 1 314 | 1ST-FL,FL 1 315 | 1STFLR,FL 1 316 | FL 1,FL 1 317 | FL01,FL 1 318 | FL1,FL 1 319 | FLOOR 1,FL 1 320 | 1FL,FL 1 321 | 11TH FL,FL 11 322 | 002F,FL 2 323 | 002FL,FL 2 324 | 002ND,FL 2 325 | 02 FL,FL 2 326 | 02FL,FL 2 327 | 02ND FL,FL 2 328 | 02ND FL R,FL 2 329 | 02ND FLR,FL 2 330 | 02NDFL,FL 2 331 | 1 2ND FL,FL 2 332 | 1FL2,FL 2 333 | 2 FL,FL 2 334 | 2 FLOOR,FL 2 335 | 2FL,FL 2 336 | 2FLF,FL 2 337 | 2FLOOR,FL 2 338 | 2FLR,FL 2 339 | 2ND FL,FL 2 340 | 2ND FL 2,FL 2 341 | 2ND FLF,FL 2 342 | 2ND FLOOR,FL 2 343 | 2ND FLR,FL 2 344 | 2NDF,FL 2 345 | 2NDFL,FL 2 346 | 2NDFLOOR,FL 2 347 | 2NDFLR,FL 2 348 | B 2ND FL,FL 2 349 | FL 2,FL 2 350 | FL02,FL 2 351 | FL2,FL 2 352 | 02FL F,FL 2 FRNT 353 | 02FL R,FL 2 REAR 354 | 03FL,FL 3 355 | 03RD FL,FL 3 356 | 3 FL,FL 3 357 | 3 FLOOR,FL 3 358 | 3FL,FL 3 359 | 3FL R,FL 3 360 | 3FLF,FL 3 361 | 3FLOOR,FL 3 362 | 3RD FL,FL 3 363 | 3RD FL 3,FL 3 364 | 3RD FLOOR,FL 3 365 | FL 3RD,FL 3 366 | 3RD FLR,FL 3 367 | 3RDFL,FL 3 368 | 3RDFLOOR,FL 3 369 | 3RDFLR,FL 3 370 | FL 3,FL 3 371 | FL03,FL 3 372 | FL3,FL 3 373 | 04FL,FL 4 374 | 4TH FL,FL 4 375 | 05FL,FL 5 376 | 5TH FL,FL 5 377 | 6TH FL,FL 6 378 | 7TH FL,FL 7 379 | 8TH FL,FL 8 380 | 9TH FL,FL 9 381 | 0FL2,FL2 382 | 2FLA,FL2 383 | FL,FLOOR 384 | FLR,FLOOR 385 | B 2ND FLOO,FLOOR 2B 386 | 1ST FLF,FRL 1 387 | 1 FRONT,FRNT 388 | 1FL FRONT,FRNT 389 | FRN,FRNT 390 | FRNT,FRNT 391 | FRONT,FRNT 392 | 1FRNT,FRNT 1 393 | 1FRONT,FRNT 1 394 | 1ST FL FRN,FRNT 1 395 | 1ST FL FRO,FRNT 1 396 | 1ST FL FRT,FRNT 1 397 | 1ST FRNT,FRNT 1 398 | 1ST FRONT,FRNT 1 399 | 2 FR,FRNT 2 400 | 2 FRONT,FRNT 2 401 | 2FL F,FRNT 2 402 | 2FL FRNT,FRNT 2 403 | 2FL FRONT,FRNT 2 404 | 2FLFRNT,FRNT 2 405 | 2FRN,FRNT 2 406 | 2FRNT,FRNT 2 407 | 2FRONT,FRNT 2 408 | 2ND FL FR,FRNT 2 409 | 2ND FL FRN,FRNT 2 410 | 2ND FL FRNT,FRNT 2 411 | 2ND FL FRO,FRNT 2 412 | 2ND FL FRONT,FRNT 2 413 | 2ND FL FRT,FRNT 2 414 | 2ND FRN,FRNT 2 415 | 2ND FRNT,FRNT 2 416 | 2ND FRONT,FRNT 2 417 | 2NDFRNT,FRNT 2 418 | 3 FR,FRNT 3 419 | 3 FRONT,FRNT 3 420 | 3FN,FRNT 3 421 | 3FR,FRNT 3 422 | 3FRNT,FRNT 3 423 | 3FRONT,FRNT 3 424 | 3RD FL FRN,FRNT 3 425 | 3RD FL FRT,FRNT 3 426 | 3RD FRONT,FRNT 3 427 | FRNT DESK,FRNT DESK 428 | FUP,FUP 429 | 0G12,G12 430 | 00G2,G2 431 | GARAGE,GARAGE 432 | GC,GC 433 | GDFL,GDFL 434 | GG,GG 435 | GH,GH 436 | GLW,GLW 437 | GRND,GROUND 438 | GROUND,GROUND 439 | GRFL,GROUND FLOOR 440 | GRND FL,GROUND FLOOR 441 | 00H1,H1 442 | H10-LG,H10-LG 443 | H11-LG,H11-LG 444 | H2-LG,H2-LG 445 | H3-LG,H3-LG 446 | H4-LG,H4-LG 447 | H5-LG,H5-LG 448 | H6-LG,H6-LG 449 | H7-LG,H7-LG 450 | H8-LG,H8-LG 451 | H9-LG,H9-LG 452 | HA,HA 453 | HB,HB 454 | HC,HC 455 | HD,HD 456 | HE,HE 457 | HF,HF 458 | HI,HI 459 | HORIZON,HORIZON 460 | HOTEL,HOTEL 461 | HUP,HUP 462 | IJ,IJ 463 | ILW,ILW 464 | 000J,J 465 | J10-LG,J10-LG 466 | J2-LG,J2-LG 467 | 00J3,J3 468 | J4-LG,J4-LG 469 | J6-LG,J6-LG 470 | J8-LG,J8-LG 471 | JUP,JUP 472 | 000K,K 473 | K-614A,K-614A 474 | KL,KL 475 | KLW,KLW 476 | 000L,L 477 | LC,LC 478 | LEVEL,LEVEL 479 | LIBRARY,LIBRARY 480 | LOWER,LOWR 481 | LOWR,LOWR 482 | LOW LEV,LOWR LEVEL 483 | LOW LEVEL,LOWR LEVEL 484 | LOWER LEVE,LOWR LEVEL 485 | LOWER LEVEL,LOWR LEVEL 486 | LUP,LUP 487 | 000M,M 488 | MEZZ,MEZZ 489 | MLW,MLW 490 | 000N,N 491 | 00N2,N2 492 | 00N3,N3 493 | NAND,NAND 494 | NUP,NUP 495 | 000O,O 496 | OFFICE,OFC 497 | OFC,OFC 498 | OLW,OLW 499 | 000P,P 500 | P103,P103 501 | P104,P104 502 | P105,P105 503 | P106,P106 504 | P107,P107 505 | P108,P108 506 | P109,P109 507 | P110,P110 508 | P111,P111 509 | P112,P112 510 | P113,P113 511 | P114,P114 512 | P115,P115 513 | P116,P116 514 | P117,P117 515 | P118,P118 516 | P119,P119 517 | P120,P120 518 | P123,P123 519 | P124,P124 520 | P126,P126 521 | P127,P127 522 | P128,P128 523 | P129,P129 524 | P130,P130 525 | P131,P131 526 | P132,P132 527 | P133,P133 528 | P134,P134 529 | P135,P135 530 | P136,P136 531 | PARKG,PARKG 532 | PARKUNI,PARKUNI 533 | PB,PB 534 | PC,PC 535 | PD,PD 536 | PE,PE 537 | PF,PF 538 | PG,PG 539 | 00PH,PH 540 | PH,PH 541 | 0PHW,PH W 542 | PH1,PH1 543 | PH105,PH105 544 | PH 108,PH108 545 | PH117,PH117 546 | PH121,PH121 547 | PH129,PH129 548 | PH130,PH130 549 | PH2,PH2 550 | PH20,PH20 551 | PH3,PH3 552 | PH4,PH4 553 | PH05,PH5 554 | PH5,PH5 555 | PH6,PH6 556 | PH7,PH7 557 | PH8,PH8 558 | PH9,PH9 559 | PHA,PHA 560 | PHB,PHB 561 | PHC,PHC 562 | PHD,PHD 563 | PHE,PHE 564 | PHF,PHF 565 | PHG,PHG 566 | PHH,PHH 567 | PHI,PHI 568 | PHJ,PHJ 569 | PHK,PHK 570 | PHL,PHL 571 | PHM,PHM 572 | PHNE,PHNE 573 | PHNW,PHNW 574 | PHO,PHO 575 | PHP,PHP 576 | PHQ,PHQ 577 | PHR,PHR 578 | PHS,PHS 579 | PHSE,PHSE 580 | PHSW,PHSW 581 | PHW,PHW 582 | PI,PI 583 | PIER 3,PIER 3 584 | PIER 5,PIER 5 585 | PJ,PJ 586 | PM,PM 587 | PN,PN 588 | PUBLIC,PUBLIC 589 | PUP,PUP 590 | PW,PW 591 | 000Q,Q 592 | QLW,QLW 593 | 000R,R 594 | 00R1,R 595 | 00R2,R2 596 | REAR,REAR 597 | 1FL R,REAR 598 | A REAR,REAR 599 | REA,REAR 600 | 1 R,REAR 1 601 | 1 REA,REAR 1 602 | 1 REAR,REAR 1 603 | 1FL REAR,REAR 1 604 | 1FL RR,REAR 1 605 | 1FLREAR,REAR 1 606 | 1FLRR,REAR 1 607 | 1REA,REAR 1 608 | 1REAR,REAR 1 609 | 1ST FL R,REAR 1 610 | 1ST FL REA,REAR 1 611 | 1ST FL REAR,REAR 1 612 | 1ST FL RR,REAR 1 613 | 1ST R,REAR 1 614 | 1ST REAR,REAR 1 615 | 1ST RR,REAR 1 616 | REAR 1,REAR 1 617 | REAR 1ST,REAR 1 618 | 2 REAR,REAR 2 619 | 2FL R,REAR 2 620 | 2FL REAR,REAR 2 621 | 2FLREAR,REAR 2 622 | 2ND FL R,REAR 2 623 | 2ND FL REA,REAR 2 624 | 2ND FL REAR,REAR 2 625 | 2ND FL RR,REAR 2 626 | 2ND FLREAR,REAR 2 627 | 2ND REAR,REAR 2 628 | 2ND RR,REAR 2 629 | 2REAR,REAR 2 630 | REAR 2,REAR 2 631 | 3 FLR REAR,REAR 3 632 | 3 REAR,REAR 3 633 | 3FL REAR,REAR 3 634 | 3RD FL R,REAR 3 635 | 3RD FL REA,REAR 3 636 | 3RD FL REAR,REAR 3 637 | 3RD FL RR,REAR 3 638 | 3RD REAR,REAR 3 639 | 3REAR,REAR 3 640 | REAR B,REAR B 641 | REAR C,REAR C 642 | REAR D,REAR D 643 | REST,REST 644 | RETAIL,RETAIL 645 | ROOFTOP,ROOFTOP 646 | ROOM,ROOM 647 | RUA,RUA 648 | RUB,RUB 649 | RUC,RUC 650 | RUP,RUP 651 | 000S,S 652 | SCLUB,SCLUB 653 | 1ST SIDE,SIDE 654 | SIDE,SIDE 655 | SLW,SLW 656 | 000T,T 657 | TERR,TER 658 | THA,THA 659 | THB,THB 660 | TIDES,TIDES 661 | TUP,TUP 662 | UC,UC 663 | UNIT,UNIT 664 | UTL,UTL 665 | 000V,V 666 | 000W,W 667 | WD,WD 668 | 000Z,Z -------------------------------------------------------------------------------- /passyunk/tests/test_lookups.py: -------------------------------------------------------------------------------- 1 | from passyunk.parser import PassyunkParser 2 | from passyunk.parser_addr import ( 3 | csv_path, 4 | 5 | create_suffix_lookup, create_name_switch_lookup, create_centerline_street_lookup, # these names didn't change 6 | create_dir_lookup, create_ordinal_lookup, create_saint_lookup, create_namestd_lookup, 7 | create_apt_lookup, create_aptstd_lookup, create_apte_lookup, 8 | 9 | Nameswitch, CenterlineName, CenterlineNameOnly, Suffix, Directional, 10 | AddrOrdinal, Saint, Namestd, Apt, AptStd, Apte, 11 | 12 | is_suffix, is_name_switch, is_centerline_name, is_centerline_street_name, 13 | is_centerline_street_pre, is_centerline_street_suffix, is_dir, is_saint, 14 | is_name_std, is_apt, is_apt_std, is_apte 15 | ) 16 | import pytest 17 | import re 18 | import subprocess 19 | 20 | """ HELPERS """ 21 | 22 | @pytest.fixture 23 | def p(): 24 | return PassyunkParser() 25 | 26 | def csv_line_count(thing): 27 | path = csv_path(thing) 28 | result = subprocess.run(['wc', '-l', path], stdout=subprocess.PIPE) 29 | return int(result.stdout.split()[0]) 30 | 31 | """ 32 | Test each create_...lookup() function to see if it exists and returns a dict 33 | of the right size, i.e. has scanned through the right csv to create keys for 34 | each row. 35 | """ 36 | 37 | def test_create_suffix_lookup(p): 38 | lookup = create_suffix_lookup() 39 | len_csv = csv_line_count('suffix') 40 | assert type(lookup) == dict 41 | print(f"CSV: {len_csv}, Lookup: {len(lookup)}") 42 | # Sometimes the lookup is slightly smaller than the csv, probably because 43 | # of repeat keys. It's never bigger 44 | assert len_csv >= len(lookup) >= int(len(lookup) * 0.99) 45 | 46 | def test_create_name_switch_lookup(p): 47 | lookup = create_name_switch_lookup() 48 | len_csv = csv_line_count('name_switch') 49 | assert type(lookup) == dict 50 | print(f"CSV: {len_csv}, Lookup: {len(lookup)}") 51 | assert len_csv >= len(lookup) >= int(len(lookup) * 0.99) 52 | 53 | # this one has to be different because it returns five things, not one 54 | def test_create_centerline_street_lookup(p): 55 | # TODO: figure out appropriate lengths for each of these 56 | lookup, lookup_list, lookup_name, lookup_pre, lookup_suffix = create_centerline_street_lookup() 57 | len_csv = csv_line_count('centerline_streets') 58 | for thing in [lookup, lookup_name, lookup_pre, lookup_suffix]: 59 | assert type(thing) == dict 60 | print(f"CSV: {len_csv}, Thing: {len(thing)}") 61 | assert len_csv >= len(thing) 62 | assert type(lookup_list) == list 63 | print(f"List: {len(lookup_list)}") 64 | assert len(lookup_list) == len_csv 65 | 66 | def test_create_dir_lookup(p): 67 | lookup = create_dir_lookup() 68 | len_csv = csv_line_count('directional') 69 | assert type(lookup) == dict 70 | print(f"CSV: {len_csv}, Lookup: {len(lookup)}") 71 | assert len_csv >= len(lookup) >= int(len(lookup) * 0.99) 72 | 73 | def test_create_ordinal_lookup(p): 74 | lookup = create_ordinal_lookup() 75 | # This one is manual since its keys are hardcoded 76 | assert type(lookup) == dict 77 | assert set(lookup.keys()) == { 78 | '1', '11', '2', '12', '3', '13', '4', '5', '6', '7', '8', '9', '0' 79 | } 80 | for key in lookup.keys(): 81 | assert type(lookup[key]) == AddrOrdinal 82 | assert lookup[key].ordigit == key 83 | if key == '1': 84 | assert lookup[key].orsuffix == 'ST' 85 | elif key == '2': 86 | assert lookup[key].orsuffix == 'ND' 87 | elif key == '3': 88 | assert lookup[key].orsuffix == 'RD' 89 | else: 90 | assert lookup[key].orsuffix == 'TH' 91 | 92 | def test_create_saint_lookup(p): 93 | lookup = create_saint_lookup() 94 | len_csv = csv_line_count('saint') 95 | assert type(lookup) == dict 96 | print(f"CSV: {len_csv}, Lookup: {len(lookup)}") 97 | assert len_csv >= len(lookup) >= int(len(lookup) * 0.99) 98 | 99 | def test_create_namestd_lookup(p): 100 | lookup = create_namestd_lookup() 101 | len_csv = csv_line_count('std') 102 | assert type(lookup) == dict 103 | print(f"CSV: {len_csv}, Lookup: {len(lookup)}") 104 | assert len_csv >= len(lookup) >= int(len(lookup) * 0.99) 105 | 106 | def test_create_apt_lookup(p): 107 | lookup = create_apt_lookup() 108 | len_csv = csv_line_count('apt') 109 | assert type(lookup) == dict 110 | print(f"CSV: {len_csv}, Lookup: {len(lookup)}") 111 | assert len_csv >= len(lookup) >= int(len(lookup) * 0.99) 112 | 113 | def test_create_aptstd_lookup(p): 114 | lookup = create_aptstd_lookup() 115 | len_csv = csv_line_count('apt_std') 116 | assert type(lookup) == dict 117 | print(f"CSV: {len_csv}, Lookup: {len(lookup)}") 118 | assert len_csv >= len(lookup) >= int(len(lookup) * 0.99) 119 | 120 | def test_create_apte_lookup(p): 121 | lookup = create_apte_lookup() 122 | len_csv = csv_line_count('apte') 123 | assert type(lookup) == dict 124 | print(f"CSV: {len_csv}, Lookup: {len(lookup)}") 125 | assert len_csv >= len(lookup) >= int(len(lookup) * 0.99) 126 | 127 | 128 | """ Test each is_...() function to see if desired behavior occurs """ 129 | 130 | def test_is_suffix(p): 131 | test0 = is_suffix("JUNKNONSENSE") # not a suffix 132 | test1 = is_suffix("AVE") # standard suffix abbr 133 | test2 = is_suffix("ALLEY") # long suffix 134 | test3 = is_suffix("SQU") # common abbr 135 | for num, resp in enumerate([test0, test1, test2, test3]): 136 | assert type(resp) == Suffix 137 | assert set(vars(resp).keys()) == {'full', 'common', 'correct', 'std'} 138 | assert resp.std == str(num) 139 | 140 | def test_is_name_switch(p): 141 | test_yes = is_name_switch("N TANEY ST") # doubles as test of TANEY/LECOUNT switch sticking 142 | test_no = is_name_switch("JUNKNONSENSE ST") 143 | for tst in [test_yes, test_no]: 144 | assert type(tst) == Nameswitch 145 | assert vars(test_yes) == {'pre': 'N', 'name': 'LECOUNT', 'suffix': 'ST', 'post': '', 'name_from': 'N TANEY ST'} 146 | assert vars(test_no) == {'pre': ' ', 'name': '0', 'suffix': ' ', 'post': ' ', 'name_from': ' '} 147 | 148 | def test_is_centerline_name(p): 149 | test_yes = is_centerline_name("E WASHINGTON LN N") # has all five fields 150 | test_no = is_centerline_name("NOTPREFIX JUNK ST NOTSUFFIX") 151 | for tst in [test_yes, test_no]: 152 | assert type(tst) == CenterlineName 153 | assert vars(test_yes) == {'full': 'E WASHINGTON LN N', 'pre': 'E', 'name': 'WASHINGTON', 'suffix': 'LN', 'post': 'N'} 154 | assert vars(test_no) == {'full': '0', 'pre': ' ', 'name': ' ', 'suffix': ' ', 'post': ' '} 155 | 156 | def test_is_centerline_street_name(p): 157 | test_yes = is_centerline_street_name("BROAD") 158 | for ix, result in enumerate(test_yes): # should be a list of length 2 159 | assert type(result) == CenterlineName 160 | assert result.full == 'N BROAD ST' if ix == 0 else 'S BROAD ST' 161 | assert result.pre == 'N' if ix == 0 else 'S' 162 | assert result.name == 'BROAD' 163 | assert result.suffix == 'ST' 164 | assert result.post == '' 165 | test_no = is_centerline_street_name("JUNK") 166 | assert test_no == [' ', 0, 0] 167 | 168 | def test_is_centerline_street_pre(p): 169 | test_yes = is_centerline_street_pre("N BROAD") # will be a list of length 1 170 | assert type(test_yes[0]) == CenterlineName 171 | assert vars(test_yes[0]) == {'full': 'N BROAD ST', 'pre': 'N', 'name': 'BROAD', 'suffix': 'ST', 'post': ''} 172 | test_no = is_centerline_street_pre("X JUNK") 173 | assert test_no == [' ', 0, 0] 174 | 175 | def test_is_centerline_street_suffix(p): 176 | test_yes = is_centerline_street_suffix("WASHINGTON LN") # will be a list of length 5: 177 | # WASHINGTON LN, E WASHINGTON LN, E WASHINGTON LN N, E WASHINGTON LN S, W WASHINGTON LN 178 | for ix, result in enumerate(test_yes): # should be a list of length 2 179 | assert type(result) == CenterlineName 180 | assert set(vars(result).keys()) == {'full', 'pre', 'name', 'suffix', 'post'} 181 | assert result.name == 'WASHINGTON' 182 | assert result.suffix == 'LN' 183 | # check that suffix, if present, matches the full street name in pdata 184 | match_ending = re.search(r'(?<= )(N|S)$', result.full) 185 | if match_ending: 186 | assert match_ending.group(0) == result.post 187 | 188 | def test_is_dir(p): 189 | full_to_common = {'NORTH': 'N', 'EAST': 'E', 'SOUTH': 'S', 'WEST': 'W'} 190 | common_to_full = {v:k for k,v in full_to_common.items()} 191 | for dir_correct in common_to_full.keys(): 192 | test_yes = is_dir(dir_correct) 193 | assert type(test_yes) == Directional 194 | assert test_yes.full == common_to_full[dir_correct] 195 | assert test_yes.common == test_yes.correct == dir_correct 196 | assert test_yes.std == '1' 197 | for dir_long in full_to_common.keys(): 198 | test_yes = is_dir(dir_long) 199 | assert type(test_yes) == Directional 200 | assert test_yes.full == test_yes.common == dir_long 201 | assert test_yes.correct == full_to_common[dir_long] 202 | assert test_yes.std == '2' 203 | test_no = is_dir("JUNK") 204 | assert type(test_no) == Directional 205 | assert vars(test_no) == {'full': ' ', 'common': 'JUNK', 'correct': ' ', 'std': '0'} 206 | 207 | def test_is_saint(p): # is_saint() returns a boolean, so just check that 208 | assert is_saint("MALACHY") 209 | assert not is_saint("ROLAND") 210 | 211 | def test_is_name_std(p): 212 | test_found = is_name_std("PAAAYUNK") 213 | assert type(test_found) == Namestd 214 | assert test_found.correct == "PASSYUNK" 215 | test_notfound = is_name_std("JUNKNONSENSE") 216 | assert type(test_notfound) == Namestd 217 | assert test_notfound.correct == '' 218 | 219 | def test_is_apt(p): 220 | test_yes = is_apt("APARTMENT") 221 | test_no = is_apt("APATOSAURUS") 222 | for tst in [test_yes, test_no]: 223 | assert type(tst) == Apt 224 | assert test_yes.correct == 'APT' 225 | assert test_no.correct == '' 226 | 227 | def test_is_apt_std(p): # is_apt_std() returns a string, so just check that 228 | assert is_apt_std("001G") == '1G' # common on left of csv, correct on right, unlike many others 229 | assert is_apt_std("JUNK") == '' 230 | 231 | def test_is_apte(p): 232 | apte_yes = is_apte('LOBBY') 233 | assert type(apte_yes) == Apte 234 | assert apte_yes.correct == 'LBBY' 235 | apte_no = is_apte('JUNK') 236 | assert type(apte_no) == Apte 237 | assert apte_no.correct == '' -------------------------------------------------------------------------------- /passyunk/centerline.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import csv 4 | import os 5 | import sys 6 | # import Levenshtein 7 | from fuzzywuzzy import process 8 | 9 | __author__ = 'tom.swanson' 10 | 11 | cwd = os.path.dirname(__file__) 12 | cwd += '/pdata' 13 | # cwd = cwd.replace('\\','/') 14 | cl_file = 'centerline' 15 | al_file = 'alias' 16 | 17 | # MAX_RANGE = 200 18 | 19 | # cfout = open(os.path.dirname(__file__)+'/sandbox/fuzzyout.csv', 'w') 20 | # print(Levenshtein.ratio('BREAD', 'BROAD')) 21 | 22 | 23 | def csv_path(file_name): 24 | return os.path.join(cwd, file_name + '.csv').replace('\\', '/') 25 | 26 | 27 | cl_basename = {} # dict 28 | cl_list = [] # array 29 | cl_name = {} 30 | cl_name_fw = [] 31 | al_basename = {} 32 | al_list = [] 33 | al_name = {} 34 | al_name_fw = [] 35 | 36 | for x in range(0, 26): 37 | cl_name_fw.append([x]) 38 | 39 | 40 | class Centerline: 41 | def __init__(self, row): 42 | self.pre = row[0].strip() 43 | self.name = row[1].strip() 44 | self.suffix = row[2].strip() 45 | self.post = row[3].strip() 46 | self.from_left = int(row[4] or 0) 47 | self.to_left = int(row[5] or 0) 48 | self.from_right = int(row[6] or 0) 49 | self.to_right = int(row[7] or 0) 50 | self.street_code = row[8] 51 | self.cl_seg_id = row[9] 52 | self.cl_responsibility = row[10].strip() 53 | self.base = '{} {} {} {}'.format(self.pre, self.name, self.suffix, self.post) 54 | self.base = ' '.join(self.base.split()) 55 | self.oeb_right = oeb(self.from_right, self.to_right) 56 | self.oeb_left = oeb(self.from_left, self.to_left) 57 | 58 | def __str__(self): 59 | return 'Centerline: {}-{} {}'.format( 60 | min(self.from_left, self.from_right), 61 | max(self.to_left, self.to_right), 62 | self.base 63 | ) 64 | 65 | def __repr__(self): 66 | return self.__str__() 67 | 68 | 69 | class Alias: 70 | def __init__(self, row): 71 | self.pre = row[0].strip() 72 | self.name = row[1].strip() 73 | self.suffix = row[2].strip() 74 | self.post = row[3].strip() 75 | self.from_left = int(row[4]) 76 | self.to_left = int(row[5]) 77 | self.from_right = int(row[6]) 78 | self.to_right = int(row[7]) 79 | self.street_code = row[8] 80 | self.cl_seg_id = row[9] 81 | self.cl_responsibility = row[10].strip() 82 | self.base = '{} {} {} {}'.format(self.pre, self.name, self.suffix, self.post) 83 | self.base = ' '.join(self.base.split()) 84 | self.oeb_right = oeb(self.from_right, self.to_right) 85 | self.oeb_left = oeb(self.from_left, self.to_left) 86 | self.cl_pre = row[11].strip() 87 | self.cl_name = row[12].strip() 88 | self.cl_suffix = row[13].strip() 89 | self.cl_post = row[14].strip() 90 | self.cl_name_full = row[15].strip() 91 | 92 | 93 | def __str__(self): 94 | return 'Centerline: {}-{} {}'.format( 95 | min(self.from_left, self.from_right), 96 | max(self.to_left, self.to_right), 97 | self.base 98 | ) 99 | 100 | def __repr__(self): 101 | return self.__str__() 102 | 103 | class NameOnly: 104 | def __init__(self, row): 105 | self.name = row[0] 106 | self.low = row[1] 107 | self.high = row[2] 108 | 109 | 110 | class NameFW: 111 | def __init__(self): 112 | self.name = '' 113 | 114 | 115 | def test_cl_file(): 116 | path = csv_path(cl_file) 117 | return os.path.isfile(path) 118 | 119 | def test_al_file(): 120 | path = csv_path(al_file) 121 | return os.path.isfile(path) 122 | 123 | def create_cl_lookup(): 124 | is_cl_file = test_cl_file() 125 | if not is_cl_file: 126 | return False 127 | path = csv_path(cl_file) 128 | f = open(path, 'r') 129 | i = 0 130 | j = 0 131 | jbase = 0 132 | p_base = '' 133 | try: 134 | reader = csv.reader(f) 135 | p_name = '' 136 | p_base = '' 137 | 138 | for row in reader: 139 | if i == 0: 140 | i += 1 141 | continue 142 | r = Centerline(row) 143 | if i == 0: 144 | rp = r 145 | c_name = r.name 146 | c_base = r.base 147 | 148 | if c_name != p_name and i != 0: 149 | ack = [p_name, j - 1, i - 1] 150 | r2 = NameOnly(ack) 151 | cl_name[p_name] = r2 152 | j = i 153 | 154 | if c_base != p_base and i != 0: 155 | ack = [p_base, jbase - 1, i - 1] 156 | r2 = NameOnly(ack) 157 | cl_basename[p_base] = r2 158 | jbase = i 159 | 160 | cl_list.append(r) 161 | rp = r 162 | p_name = c_name 163 | p_base = c_base 164 | i += 1 165 | 166 | except IOError: 167 | print('Error opening ' + path, sys.exc_info()[0]) 168 | ack = [p_base, jbase - 1, i - 1] 169 | r2 = NameOnly(ack) 170 | cl_basename[p_base] = r2 171 | validate_cl_basename() 172 | 173 | create_cl_name_fw() 174 | 175 | f.close() 176 | return True 177 | 178 | 179 | def create_al_lookup(): 180 | is_al_file = test_al_file() 181 | if not is_al_file: 182 | return False 183 | path = csv_path(al_file) 184 | f = open(path, 'r') 185 | i = 0 186 | j = 0 187 | jbase = 0 188 | p_base = '' 189 | try: 190 | reader = csv.reader(f) 191 | p_name = '' 192 | p_base = '' 193 | 194 | for row in reader: 195 | if i == 0: 196 | i += 1 197 | continue 198 | r = Alias(row) 199 | if i == 0: 200 | rp = r 201 | c_name = r.name 202 | c_base = r.base 203 | 204 | if c_name != p_name and i != 0: 205 | ack = [p_name, j - 1, i - 1] 206 | r2 = NameOnly(ack) 207 | al_name[p_name] = r2 208 | j = i 209 | 210 | if c_base != p_base and i != 0: 211 | ack = [p_base, jbase - 1, i - 1] 212 | r2 = NameOnly(ack) 213 | al_basename[p_base] = r2 214 | jbase = i 215 | 216 | al_list.append(r) 217 | rp = r 218 | p_name = c_name 219 | p_base = c_base 220 | i += 1 221 | 222 | except IOError: 223 | print('Error opening ' + path, sys.exc_info()[0]) 224 | ack = [p_base, jbase - 1, i - 1] 225 | r2 = NameOnly(ack) 226 | al_basename[p_base] = r2 227 | 228 | validate_al_basename() 229 | 230 | # create_al_name_fw() 231 | 232 | f.close() 233 | return True 234 | 235 | 236 | def create_cl_name_fw(): 237 | # cl_name.sort() 238 | for item in cl_name: 239 | if item == '': 240 | continue 241 | if len(item) <= 4: 242 | continue 243 | i = ord(item[0]) 244 | if i < 65 or i > 90: 245 | continue 246 | i -= 65 247 | try: 248 | cl_name_fw[i].append(item) 249 | except Exception: 250 | print('Exception loading Centerline for fw ' + item + ' ' + str(i), sys.exc_info()[0]) 251 | 252 | for item in cl_name_fw: 253 | # print(item[0],len(item)) 254 | item.pop(0) 255 | item.sort() 256 | return 257 | 258 | 259 | def oeb(fr, to): 260 | ret = 'U' 261 | if fr == '' or fr == '0' or to == '' or to == '0': 262 | return 'U' 263 | 264 | if fr % 2 == 0: 265 | ret = 'E' 266 | else: 267 | ret = 'O' 268 | if to % 2 == 0: 269 | ret_to = 'E' 270 | else: 271 | ret_to = 'O' 272 | 273 | if ret != ret_to: 274 | return 'B' 275 | 276 | return ret 277 | 278 | 279 | def validate_cl_basename(): 280 | for r in cl_basename: 281 | row = cl_basename[r] 282 | row_list = cl_list[row.low:row.high] 283 | name = {} 284 | for st in row_list: 285 | name[st.base] = st.base 286 | if len(name) > 1: 287 | for ack in name: 288 | print(ack) 289 | 290 | def validate_al_basename(): 291 | for r in al_basename: 292 | row = al_basename[r] 293 | row_list = al_list[row.low:row.high] 294 | name = {} 295 | for st in row_list: 296 | name[st.base] = st.base 297 | if len(name) > 1: 298 | for ack in name: 299 | print(ack) 300 | 301 | def is_cl_base(test): 302 | try: 303 | name = cl_basename[test] 304 | except KeyError: 305 | row = [] 306 | # row.append([' ', 0, 0]) 307 | return row 308 | return cl_list[name.low:name.high] 309 | 310 | def is_al_base(test): 311 | try: 312 | name = al_basename[test] 313 | except KeyError: 314 | row = [] 315 | # row.append([' ', 0, 0]) 316 | return row 317 | return al_list[name.low:name.high] 318 | 319 | def is_cl_name(test): 320 | try: 321 | name = cl_name[test] 322 | except KeyError: 323 | row = [] 324 | # row.append([' ', 0, 0]) 325 | return row 326 | return cl_list[name.low:name.high] 327 | 328 | def is_al_name(test): 329 | try: 330 | name = al_name[test] 331 | except KeyError: 332 | row = [] 333 | # row.append([' ', 0, 0]) 334 | return row 335 | return al_list[name.low:name.high] 336 | 337 | def get_cl_info(address, addr_uber, MAX_RANGE): 338 | addr_low_num = address.address.low_num 339 | addr_parity = address.address.parity 340 | addr_street_full = address.street.full 341 | # Get matching centerlines based on street name 342 | centerlines = is_cl_base(addr_street_full) 343 | # If there are matches 344 | if len(centerlines) > 0: 345 | matches = [] 346 | cur_closest = None 347 | cur_closest_offset = None 348 | cur_closest_addr = 0 349 | 350 | # Loop over matches 351 | for cl in centerlines: 352 | from_left = cl.from_left 353 | from_right = cl.from_right 354 | to_left = cl.to_left 355 | to_right = cl.to_right 356 | 357 | # Try to match on the left 358 | if from_left <= addr_low_num <= to_left and \ 359 | cl.oeb_left == addr_parity: 360 | matches.append(cl) 361 | # Try to match on the right 362 | elif from_right <= addr_low_num <= to_right and \ 363 | cl.oeb_right == addr_parity: 364 | matches.append(cl) 365 | # If no matches, find the one with the nearest address range 366 | else: 367 | if from_left == 0: 368 | continue 369 | 370 | # Loop over addresses in range to find the min 371 | if cur_closest is None or \ 372 | abs(from_left - addr_low_num) < cur_closest_offset: 373 | cur_closest_offset = abs(from_left - addr_low_num) 374 | cur_closest = cl 375 | cur_closest_addr = from_left 376 | if abs(from_right - addr_low_num) < cur_closest_offset: 377 | cur_closest_offset = abs(from_right - addr_low_num) 378 | cur_closest = cl 379 | cur_closest_addr = from_right 380 | if abs(addr_low_num - to_left) < cur_closest_offset: 381 | cur_closest_offset = abs(addr_low_num - to_left) 382 | cur_closest = cl 383 | cur_closest_addr = to_left 384 | if abs(addr_low_num - to_right) < cur_closest_offset: 385 | cur_closest_offset = abs(addr_low_num - to_right) 386 | cur_closest = cl 387 | cur_closest_addr = to_right 388 | 389 | if len(matches) == 0: 390 | # good street name but no matching address range 391 | aliases = is_al_base(addr_street_full) 392 | # Check for alias 393 | if len(aliases) > 0: 394 | matches = [] 395 | cur_closest = None 396 | cur_closest_offset = None 397 | cur_closest_addr = 0 398 | 399 | # Loop over matches 400 | for al in aliases: 401 | from_left = al.from_left 402 | from_right = al.from_right 403 | to_left = al.to_left 404 | to_right = al.to_right 405 | 406 | # Try to match on the left 407 | if from_left <= addr_low_num <= to_left and \ 408 | al.oeb_left == addr_parity: 409 | # address.street.full = al.cl_name_full 410 | # address.street.pre = al.cl_pre 411 | # address.street.name = al.cl_name 412 | # address.street.suffix = al.cl_suffix 413 | # address.street.post = al.cl_post 414 | addr_uber.components.street.full = al.cl_name_full 415 | addr_uber.components.street.predir = al.cl_pre 416 | addr_uber.components.street.name = al.cl_name 417 | addr_uber.components.street.suffix = al.cl_suffix 418 | addr_uber.components.street.postdir = al.cl_post 419 | 420 | return get_cl_info(address, addr_uber, MAX_RANGE) 421 | 422 | # Try to match on the right 423 | elif from_right <= addr_low_num <= to_right and \ 424 | al.oeb_right == addr_parity: 425 | # address.street.full = al.cl_name_full 426 | # address.street.pre = al.cl_pre 427 | # address.street.name = al.cl_name 428 | # address.street.suffix = al.cl_suffix 429 | # address.street.post = al.cl_post 430 | addr_uber.components.street.full = al.cl_name_full 431 | addr_uber.components.street.predir = al.cl_pre 432 | addr_uber.components.street.name = al.cl_name 433 | addr_uber.components.street.suffix = al.cl_suffix 434 | addr_uber.components.street.postdir = al.cl_post 435 | 436 | return get_cl_info(address, addr_uber, MAX_RANGE) 437 | 438 | if len(matches) == 0: 439 | # Didn't find an alias match 440 | if addr_low_num == -1 and cur_closest is not None: 441 | address.street.street_code = cur_closest.street_code 442 | address.street.is_centerline_match = True 443 | return 444 | 445 | if cur_closest_offset is not None and cur_closest_offset < MAX_RANGE: 446 | address.street.street_code = cur_closest.street_code 447 | address.cl_seg_id = cur_closest.cl_seg_id 448 | address.cl_responsibility = cur_closest.cl_responsibility 449 | address.cl_addr_match = 'RANGE:' + str(cur_closest_offset) 450 | address.address.full = str(cur_closest_addr) 451 | return 452 | 453 | # Treat as a Street Match 454 | if addr_uber.type != 'intersection_addr': 455 | addr_uber.type = 'street' 456 | address.street.street_code = cl.street_code 457 | address.cl_addr_match = 'MATCH TO STREET. ADDR NUMBER NO MATCH' 458 | return 459 | 460 | # Exact Match 461 | if len(matches) == 1: 462 | match = matches[0] 463 | address.street.street_code = match.street_code 464 | address.cl_seg_id = match.cl_seg_id 465 | address.cl_responsibility = match.cl_responsibility 466 | address.cl_addr_match = 'A' 467 | return 468 | 469 | # Exact Street match, multiple range matches, return the count of matches 470 | if len(matches) > 1: 471 | address.street.street_code = cl.street_code 472 | address.cl_addr_match = 'AM' 473 | # address.cl_addr_match = str(len(matches)) 474 | return 475 | # If there are no matching centerline names, check for alias 476 | aliases = is_al_base(addr_street_full) 477 | if len(aliases) > 0: 478 | matches = [] 479 | cur_closest = None 480 | cur_closest_offset = None 481 | cur_closest_addr = 0 482 | 483 | # Loop over matches 484 | for al in aliases: 485 | from_left = al.from_left 486 | from_right = al.from_right 487 | to_left = al.to_left 488 | to_right = al.to_right 489 | 490 | # Try to match on the left 491 | if from_left <= addr_low_num <= to_left and \ 492 | al.oeb_left == addr_parity: 493 | # address.street.full = al.cl_name_full 494 | # address.street.pre = al.cl_pre 495 | # address.street.name = al.cl_name 496 | # address.street.suffix = al.cl_suffix 497 | # address.street.post = al.cl_post 498 | addr_uber.components.street.full = al.cl_name_full 499 | addr_uber.components.street.predir = al.cl_pre 500 | addr_uber.components.street.name = al.cl_name 501 | addr_uber.components.street.suffix = al.cl_suffix 502 | addr_uber.components.street.postdir = al.cl_post 503 | 504 | return get_cl_info(address, addr_uber, MAX_RANGE) 505 | 506 | # Try to match on the right 507 | elif from_right <= addr_low_num <= to_right and \ 508 | al.oeb_right == addr_parity: 509 | # address.street.full = al.cl_name_full 510 | # address.street.pre = al.cl_pre 511 | # address.street.name = al.cl_name 512 | # address.street.suffix = al.cl_suffix 513 | # address.street.post = al.cl_post 514 | addr_uber.components.street.full = al.cl_name_full 515 | addr_uber.components.street.predir = al.cl_pre 516 | addr_uber.components.street.name = al.cl_name 517 | addr_uber.components.street.suffix = al.cl_suffix 518 | addr_uber.components.street.postdir = al.cl_post 519 | 520 | return get_cl_info(address, addr_uber, MAX_RANGE) 521 | # If we didn't find a match using the street base (e.g. N 10TH ST), try 522 | # using the street name (10TH). 523 | centerlines = is_cl_name(address.street.name) 524 | if len(centerlines) > 0: 525 | matches = [] 526 | for row in centerlines: 527 | if row.from_left <= addr_low_num <= row.to_left and row.oeb_left == addr_parity: 528 | if (address.street.predir != '' and address.street.predir == row.pre) or ( 529 | address.street.predir == '' and row.pre == '') or ( 530 | address.street.predir == '' and row.pre != '') or ( 531 | address.street.predir != '' and row.pre == ''): 532 | if (address.street.postdir != '' and address.street.postdir == row.post) or ( 533 | address.street.postdir == '' and row.post == '') or ( 534 | address.street.postdir == '' and row.post != '') or ( 535 | address.street.postdir != '' and row.post == ''): 536 | if (address.street.suffix != '' and address.street.suffix == row.suffix) or ( 537 | address.street.suffix == '' and row.suffix == '') or ( 538 | address.street.suffix == '' and row.suffix != '') or ( 539 | address.street.suffix != '' and row.suffix == ''): 540 | matches.append(row) 541 | elif row.from_right <= addr_low_num <= row.to_right and row.oeb_right == addr_parity: 542 | if (address.street.predir != '' and address.street.predir == row.pre) or ( 543 | address.street.predir == '' and row.pre == '') or ( 544 | address.street.predir == '' and row.pre != '') or ( 545 | address.street.predir != '' and row.pre == ''): 546 | if (address.street.postdir != '' and address.street.postdir == row.post) or ( 547 | address.street.postdir == '' and row.post == '') or ( 548 | address.street.postdir == '' and row.post != '') or ( 549 | address.street.postdir != '' and row.post == ''): 550 | if (address.street.suffix != '' and address.street.suffix == row.suffix) or ( 551 | address.street.suffix == '' and row.suffix == '') or ( 552 | address.street.suffix == '' and row.suffix != '') or ( 553 | address.street.suffix != '' and row.suffix == ''): 554 | matches.append(row) 555 | 556 | # Let's just see if a match to the street name can be made. If there is only one match, it should be safe to use 557 | # at this point. The only difference in logic below from above is that suffixes do not have to match 558 | # 1018 ALPENA DR - should find RD 559 | if len(matches) == 0: 560 | matches = [] 561 | for row in centerlines: 562 | if row.from_left <= addr_low_num <= row.to_left and row.oeb_left == addr_parity: 563 | if (address.street.predir != '' and address.street.predir == row.pre) or ( 564 | address.street.predir == '' and row.pre == '') or ( 565 | address.street.predir == '' and row.pre != '') or ( 566 | address.street.predir != '' and row.pre == ''): 567 | if (address.street.postdir != '' and address.street.postdir == row.post) or ( 568 | address.street.postdir == '' and row.post == '') or ( 569 | address.street.postdir == '' and row.post != '') or ( 570 | address.street.postdir != '' and row.post == ''): 571 | if (address.street.suffix != '' and address.street.suffix != row.suffix) or ( 572 | address.street.suffix == '' and row.suffix == '') or ( 573 | address.street.suffix == '' and row.suffix != '') or ( 574 | address.street.suffix != '' and row.suffix == ''): 575 | matches.append(row) 576 | elif row.from_right <= addr_low_num <= row.to_right and row.oeb_right == addr_parity: 577 | if (address.street.predir != '' and address.street.predir == row.pre) or ( 578 | address.street.predir == '' and row.pre == '') or ( 579 | address.street.predir == '' and row.pre != '') or ( 580 | address.street.predir != '' and row.pre == ''): 581 | if (address.street.postdir != '' and address.street.postdir == row.post) or ( 582 | address.street.postdir == '' and row.post == '') or ( 583 | address.street.postdir == '' and row.post != '') or ( 584 | address.street.postdir != '' and row.post == ''): 585 | if (address.street.suffix != '' and address.street.suffix != row.suffix) or ( 586 | address.street.suffix == '' and row.suffix == '') or ( 587 | address.street.suffix == '' and row.suffix != '') or ( 588 | address.street.suffix != '' and row.suffix == ''): 589 | matches.append(row) 590 | 591 | if len(matches) == 0: 592 | address.cl_addr_match = 'NONE' 593 | return 594 | 595 | if len(matches) > 1: 596 | address.cl_addr_match = 'MULTI2' 597 | return 598 | 599 | if len(matches) == 1: 600 | match = matches[0] 601 | match_type = 'SS' 602 | 603 | if address.street.predir != match.pre: 604 | match_type += ' Pre' 605 | address.street.predir = match.pre 606 | if address.street.postdir != match.post: 607 | match_type += ' Post' 608 | address.street.postdir = match.post 609 | if address.street.suffix != match.suffix: 610 | match_type += ' Suffix' 611 | address.street.suffix = match.suffix 612 | address.street.street_code = match.street_code 613 | address.cl_seg_id = match.cl_seg_id 614 | address.cl_responsibility = match.cl_responsibility 615 | address.cl_addr_match = match_type 616 | return 617 | 618 | if len(matches) == 1: 619 | match = matches[0] 620 | match_type = 'B' 621 | 622 | if address.street.predir != match.pre: 623 | match_type += ' Pre' 624 | address.street.predir = match.pre 625 | if address.street.postdir != match.post: 626 | match_type += ' Post' 627 | address.street.postdir = match.post 628 | # The postdir was parsed to unit - 1 S SCHUYLKILL AV W, Need to removed unit now that post dir was 629 | # added back in 630 | if address.street.postdir == address.address_unit.unit_num and address.address_unit.unit_type == '#': 631 | address.address_unit.unit_num = '' 632 | address.address_unit.unit_type = '' 633 | 634 | if address.street.suffix != match.suffix: 635 | match_type += ' Suffix' 636 | address.street.suffix = match.suffix 637 | address.street.street_code = match.street_code 638 | address.cl_seg_id = match.cl_seg_id 639 | address.cl_responsibility = match.cl_responsibility 640 | address.cl_addr_match = match_type 641 | return 642 | 643 | # need to resolve dir and/or suffix 644 | if len(matches) > 1: 645 | address.street.street_code = row.street_code 646 | address.cl_addr_match = 'MULTI' # str(len(matches)) 647 | return 648 | 649 | if len(address.street.name) > 3 and address.street.name.isalpha(): 650 | # no CL match yet, try fuzzy 651 | i = ord(address.street.name[0]) 652 | if i < 65 or i > 90: 653 | print('Invalid street name for fuzzy match: ' + address.street.name) 654 | i -= 65 655 | 656 | # match scores of 90 are very suspect using this method 657 | options = process.extract(address.street.name, cl_name_fw[i], limit=2) 658 | 659 | tie = '' 660 | # print(input_) 661 | # print(options) 662 | if len(options) > 0 and options[0][0][0] == address.street.name[0] and len(address.street.name) > 3 and \ 663 | int(options[0][1]) >= 91 and abs(len(address.street.name) - len(options[0][0])) <= 4: 664 | if len(options) > 1 and options[0][1] == options[1][1]: 665 | tie_print = addr_uber.input_address + ',' + address.street.name + ',' + options[0][0] + ',' + str( 666 | options[0][1]) + ',' + options[1][0] + ',' + str( 667 | options[1][1]) 668 | # print(tie_print) 669 | tie = 'Y' 670 | # out = input_ + ',' + address.street.name + ',' + options[0][0] + ',' + str(options[0][1])+ ',' + tie+'\n' 671 | # cfout.write(out) 672 | # cfout.flush() 673 | address.street.name = options[0][0] 674 | address.street.score = tie + str(options[0][1]) 675 | 676 | get_cl_info(address, addr_uber, MAX_RANGE) 677 | return 678 | #TODO: Add attempts to match on alias street name w/ and w/o suffix as well as fuzzy matching 679 | # simple method for adding street_code to street_2 680 | def get_cl_info_street2(address): 681 | 682 | # Get matching centerlines based on street name 683 | centerlines = is_cl_base(address.street_2.full) 684 | 685 | # If there are matches 686 | if len(centerlines) > 0: 687 | address.street_2.street_code = centerlines[0].street_code 688 | return 689 | 690 | -------------------------------------------------------------------------------- /passyunk/parser.py: -------------------------------------------------------------------------------- 1 | """ 2 | Philadelphia Address Standardizer 3 | 4 | Author: Tom Swanson 5 | Revisions: James Midkiff 6 | 7 | Created: 8/25/2014 8 | Last Updated: 2/9/2016 9 | Revised: 10/2022 10 | """ 11 | 12 | from __future__ import absolute_import 13 | import csv 14 | import os 15 | import re 16 | import sys 17 | import logging 18 | import importlib_metadata 19 | import requests 20 | from copy import deepcopy 21 | 22 | # Public Data 23 | from .centerline import create_cl_lookup, get_cl_info, get_cl_info_street2, create_al_lookup # centerline.csv, alias.csv - Public 24 | from .data import opa_account_re, zipcode_re, po_box_re, mapreg_re, AddrType, \ 25 | ILLEGAL_CHARS_RE, APTFLOOR # suffix.csv - Public 26 | from .parser_addr import parse_addr_1, name_switch, is_centerline_street_name, is_centerline_street_pre, \ 27 | is_centerline_street_suffix, is_centerline_name, Address # suffix.csv, name_switch.csv, centerline_streets.csv, directional.csv, saint.csv, std.csv, apt.csv, apt_std.csv, apte.csv - Public 28 | from .landmark import Landmark # landmarks.csv - Public 29 | from .pdata import version 30 | 31 | # Private Data 32 | try: 33 | from passyunk_automation.zip4 import create_zip4_lookup, get_zip_info # usps_zip4s.csv - Private 34 | from passyunk_automation.election import create_election_lookup, get_election_info # election_block.csv - Private 35 | private_installed = True 36 | except ModuleNotFoundError as e: 37 | private_installed = False 38 | 39 | is_cl_file = False 40 | is_al_file = False 41 | is_election_file = False 42 | is_zip_file = False 43 | 44 | 45 | class AddressUber: 46 | def __init__(self): 47 | self.components = Address() 48 | self.input_address = '' 49 | self.type = '' 50 | 51 | def __repr__(self): 52 | return self.__str__() 53 | 54 | 55 | class Nameswitch: 56 | def __init__(self, row): 57 | self.pre = row[0] 58 | self.name = row[1] 59 | self.suffix = row[2] 60 | self.post = row[3] 61 | self.name_from = row[4] 62 | 63 | 64 | class CenterlineName: 65 | def __init__(self, row): 66 | self.full = row[0] 67 | self.pre = row[1] 68 | self.name = row[2] 69 | self.suffix = row[3] 70 | self.post = row[4] 71 | 72 | 73 | class CenterlineNameOnly: 74 | def __init__(self, row): 75 | self.name = row[0] 76 | self.low = row[1] 77 | self.high = row[2] 78 | 79 | ''' 80 | SETUP FUNCTIONS 81 | ''' 82 | 83 | def csv_path(file_name): 84 | return os.path.join(cwd, file_name + '.csv') 85 | 86 | def create_name_switch_lookup(): 87 | path = csv_path('name_switch') 88 | f = open(path, 'r') 89 | lookup = {} 90 | try: 91 | reader = csv.reader(f) 92 | for row in reader: 93 | r = Nameswitch(row) 94 | lookup[r.name_from] = r 95 | except IOError: 96 | print('Error opening ' + path, sys.exc_info()[0]) 97 | f.close() 98 | return lookup 99 | 100 | def create_centerline_street_lookup(): 101 | path = csv_path('centerline_streets') 102 | f = open(path, 'r') 103 | lookup = {} 104 | lookup_name = {} 105 | lookup_pre = {} 106 | lookup_suffix = {} 107 | lookup_list = [] 108 | i = 0 109 | j = 0 110 | jpre = 0 111 | jsuff = 0 112 | 113 | try: 114 | reader = csv.reader(f) 115 | previous = '' 116 | rp = '' 117 | previous_pre_name = '' 118 | previous_suffix = '' 119 | for row in reader: 120 | r = CenterlineName(row) 121 | if i == 0: 122 | rp = r 123 | current = r.name 124 | current_pre_name = r.pre + ' ' + r.name 125 | current_suffix = r.name + ' ' + r.suffix 126 | if current != previous and i != 0: 127 | ack = [previous, j, i] 128 | r2 = CenterlineNameOnly(ack) 129 | lookup_name[previous] = r2 130 | j = i 131 | if current_pre_name != previous_pre_name and i != 0: 132 | ack = [previous_pre_name, jpre, i] 133 | r2 = CenterlineNameOnly(ack) 134 | if rp.pre != '': 135 | lookup_pre[previous_pre_name] = r2 136 | jpre = i 137 | if current_suffix != previous_suffix and i != 0: 138 | ack = [previous_suffix, jsuff, i] 139 | r2 = CenterlineNameOnly(ack) 140 | if rp.suffix != '': 141 | lookup_suffix[previous_suffix] = r2 142 | jsuff = i 143 | lookup_list.append(r) 144 | lookup[r.full] = r 145 | rp = r 146 | previous = current 147 | previous_pre_name = current_pre_name 148 | previous_suffix = current_suffix 149 | i += 1 150 | 151 | except IOError: 152 | print('Error opening ' + path, sys.exc_info()[0]) 153 | f.close() 154 | return lookup, lookup_list, lookup_name, lookup_pre, lookup_suffix 155 | 156 | def create_full_names(address, addr_type): 157 | if addr_type == AddrType.opa_account or addr_type == AddrType.zipcode: 158 | return address 159 | 160 | temp = '%s %s %s %s' % (address.street.predir, address.street.name, address.street.suffix, address.street.postdir) 161 | address.street.full = ' '.join(temp.split()) 162 | temp = '%s %s %s %s' % ( 163 | address.street_2.predir, address.street_2.name, address.street_2.suffix, address.street_2.postdir) 164 | address.street_2.full = ' '.join(temp.split()) 165 | 166 | if address.address.isaddr: 167 | # no Range 168 | if address.address.low_num >= 0 > address.address.high_num: 169 | address.address.full = address.address.low 170 | if address.address.fractional != '': 171 | address.address.full = address.address.full + ' ' + address.address.fractional 172 | 173 | # return a full range - if there's a fraction, ignore 174 | elif addr_type == AddrType.block and address.address.low_num != address.address.high_num: 175 | address.address.full = address.address.low + '-' + address.address.high 176 | elif address.address.addrnum_type == 'NNNNA-NN' or address.address.addrnum_type == 'NNNN-NN': 177 | # city address range 178 | temp_num = address.address.high_num % 100 179 | if temp_num < 10: 180 | temp = '0' + str(temp_num) 181 | else: 182 | temp = str(temp_num) 183 | if address.address.fractional != '': 184 | address.address.full = address.address.low + '-' + temp + ' ' + address.address.fractional 185 | else: 186 | address.address.full = address.address.low + '-' + temp 187 | else: 188 | 189 | if address.address.fractional != '': 190 | address.address.full = address.address.low + '-' + \ 191 | address.address.high + ' ' + \ 192 | address.address.fractional 193 | elif address.address.low != '' and address.address.high != '': 194 | address.address.full = address.address.low + '-' + address.address.high 195 | else: 196 | address.address.full = '' 197 | # if you have an address number, use it even if it is and intersections 198 | if address.address.isaddr: 199 | address.base_address = address.address.full + ' ' + address.street.full 200 | addr_type == AddrType.address 201 | elif addr_type == AddrType.intersection_addr: 202 | address.base_address = address.street.full + ' & ' + address.street_2.full 203 | else: 204 | address.base_address = address.street.full 205 | 206 | return address 207 | 208 | def centerline_rematch(address): 209 | # If only street name provided and there is only one version of the full name... 210 | # 1234 Berks => 1234 Berks St 211 | 212 | if address.predir == '' and address.postdir == '' and address.suffix == '': 213 | test = address.name 214 | centerline_name = is_centerline_street_name(test) 215 | if len(centerline_name) == 1: 216 | address.predir = centerline_name[0].pre 217 | address.name = centerline_name[0].name 218 | address.suffix = centerline_name[0].suffix 219 | address.postdir = centerline_name[0].post 220 | address.parse_method = 'CL_N' 221 | address.is_centerline_match = True 222 | return 223 | if len(test) > 3 and test[-1] == 'S': 224 | test = test[0:-1] 225 | centerline_name = is_centerline_street_name(test) 226 | if len(centerline_name) == 1: 227 | address.predir = centerline_name[0].pre 228 | address.name = centerline_name[0].name 229 | address.suffix = centerline_name[0].suffix 230 | address.postdir = centerline_name[0].post 231 | address.parse_method = 'CL_N' 232 | address.is_centerline_match = True 233 | return 234 | 235 | if address.predir != '' and address.postdir == '' and address.suffix == '': 236 | test = address.predir + ' ' + address.name 237 | centerline_name = is_centerline_street_pre(test) 238 | if len(centerline_name) == 1 and centerline_name[0].suffix != '': 239 | address.predir = centerline_name[0].pre 240 | address.name = centerline_name[0].name 241 | address.suffix = centerline_name[0].suffix 242 | address.postdir = centerline_name[0].post 243 | address.parse_method = 'CL_P' 244 | address.is_centerline_match = True 245 | 246 | if address.predir == '' and address.postdir == '' and address.suffix != '': 247 | test = address.name + ' ' + address.suffix 248 | centerline_name = is_centerline_street_suffix(test) 249 | if len(centerline_name) == 1 and centerline_name[0].pre != '': 250 | address.predir = centerline_name[0].pre 251 | address.name = centerline_name[0].name 252 | address.suffix = centerline_name[0].suffix 253 | address.postdir = centerline_name[0].post 254 | address.parse_method = 'CL_S' 255 | address.is_centerline_match = True 256 | 257 | def xy_check(item): 258 | # latlon wgs84 Lon -75.0 to - 74 Lat 39 to 40 259 | # state plane Y - 2,600,000 2,800,000 X - 200,000 to 320,000 260 | tmp = item.strip() 261 | tmp = tmp.replace('+', '') 262 | tmp = tmp.replace(',', ' ') 263 | tmp = ' '.join(tmp.split()) 264 | tokens = tmp.split(' ') 265 | if len(tokens) != 2: 266 | return 267 | 268 | try: 269 | y = float(tokens[0]) 270 | except: 271 | return 272 | try: 273 | x = float(tokens[1]) 274 | except: 275 | return 276 | 277 | if y < -74.0 and y > -76.0 and x < 41.0 and x > 39.0: 278 | return 'L%.6f,%.6f' % (y, x) 279 | elif y < 2800000.0 and y > 2600000.0 and x < 320000.0 and x > 200000.0: 280 | return 'S%.6f,%.6f' % (y, x) 281 | else: 282 | return 'JUNK' 283 | 284 | def parse(item, MAX_RANGE): 285 | address_uber = AddressUber() 286 | address = address_uber.components 287 | item = '' if item == None else item 288 | is_xy = xy_check(item) 289 | if not is_xy: 290 | item = input_cleanup(address_uber, item) 291 | 292 | if item == '': 293 | address_uber.type = AddrType.none 294 | 295 | # if you get a 9 digit numeric, treat it as an OPA account 296 | opa_account_search = opa_account_re.search(item) 297 | regmap_search = mapreg_re.search(item) 298 | zipcode_search = zipcode_re.search(item) 299 | po_box_search = po_box_re.search(item) 300 | landmark = Landmark(item) 301 | 302 | if is_xy: 303 | address_uber.input_address = item 304 | if is_xy[0] == 'L': 305 | address_uber.type = AddrType.latlon 306 | address_uber.components.output_address = is_xy[1:] 307 | elif is_xy[0] == 'S': 308 | address_uber.type = AddrType.stateplane 309 | address_uber.components.output_address = is_xy[1:] 310 | else: 311 | address_uber.type = AddrType.none 312 | 313 | elif len(item) == 9 and opa_account_search: 314 | address_uber.components.output_address = item 315 | address_uber.type = AddrType.opa_account 316 | 317 | elif (len(item) == 10 or len(item) == 11) and regmap_search: 318 | address_uber.components.output_address = item.replace('-', '') 319 | address_uber.type = AddrType.mapreg 320 | 321 | elif len(item) == 5 and zipcode_search: 322 | if 19100 <= int(item) <= 19199: 323 | address_uber.components.output_address = item 324 | address_uber.type = AddrType.zipcode 325 | else: 326 | address_uber.components.output_address = item 327 | address_uber.type = AddrType.none 328 | 329 | elif ' AND ' in item and item[-8:] != ' A AND B': 330 | tokens = item.split(' AND ') 331 | if tokens[0][:5] == 'NEAR ': 332 | tokens[0] = tokens[0][5:] 333 | address = parse_addr_1(address, tokens[0]) 334 | # for some reason there are numerous addresses like this in the logs - 127 VASSAR ST/LI 335 | if tokens[1] == 'LI': 336 | address_uber.type = AddrType.address 337 | else: 338 | address2 = Address() 339 | address2 = parse_addr_1(address2, tokens[1]) 340 | address.street_2 = address2.street 341 | address_uber.type = AddrType.intersection_addr 342 | 343 | elif po_box_search: 344 | search = po_box_re.search(item) 345 | num = search.group('num') 346 | address_uber.type = AddrType.pobox 347 | address.street.name = 'PO BOX {}'.format(num) 348 | 349 | else: 350 | address = parse_addr_1(address, item) 351 | if address.street.parse_method == 'UNK': 352 | address_uber.type = AddrType.none 353 | address_uber.components.output_address = item 354 | else: 355 | if address.address.isaddr and address_uber.type != AddrType.block: 356 | if address.address.addrnum_type == 'RANGE': 357 | # address_uber.type = AddrType.range leaving in case we revisit this logic 358 | address_uber.type = AddrType.address 359 | else: 360 | address_uber.type = AddrType.address 361 | if address.street.name == '': 362 | raise ValueError('Parsed address does not have a street name: {}'.format(item)) 363 | elif address_uber.type != AddrType.block: 364 | address_uber.type = AddrType.none 365 | 366 | if address.street.parse_method != 'cl_name_match': 367 | name_switch(address) 368 | 369 | if address_uber.type == AddrType.none and address.street.parse_method == 'cl_name_match': 370 | address_uber.type = AddrType.street 371 | address_uber.components.street.is_centerline_match = True 372 | 373 | if address_uber.type == AddrType.address and not address_uber.components.street.is_centerline_match: 374 | centerline_rematch(address.street) 375 | 376 | if address_uber.type == AddrType.intersection_addr: 377 | centerline_rematch(address.street) 378 | centerline_rematch(address.street_2) 379 | 380 | if address_uber.components.cl_seg_id != '': 381 | address_uber.components.street.is_centerline_match = True 382 | 383 | create_full_names(address, address_uber.type) 384 | 385 | # create copy of address_uber before alias changes 386 | address_uber_copy = deepcopy(address_uber) 387 | address_copy = deepcopy(address) 388 | # if the users doesn't have the centerline file, parser will still work 389 | if is_cl_file: 390 | get_cl_info(address, address_uber, MAX_RANGE) 391 | if address_uber.components.street.is_centerline_match and address_uber.type == AddrType.none: 392 | address_uber.type = AddrType.street 393 | if address_uber.type == 'intersection_addr': 394 | get_cl_info_street2(address) 395 | if address.street_2.street_code: 396 | address_uber.components.street_2.is_centerline_match = True 397 | 398 | # check if landmark if address_uber.type = none, street or = intersection_addr with at least one non-matching street 399 | if address_uber.type == AddrType.none or (address_uber.type == AddrType.intersection_addr and ( 400 | address_uber.components.street.is_centerline_match == False or address_uber.components.street_2.is_centerline_match == False)): 401 | landmark.landmark_check() 402 | if landmark.is_landmark: 403 | item = landmark.landmark_address 404 | address = parse_addr_1(address, item) 405 | # Hack to process address steps below: 406 | address_uber.type = AddrType.address 407 | get_cl_info(address, address_uber, MAX_RANGE) 408 | if address_uber.components.street.is_centerline_match and address_uber.type == AddrType.none: 409 | address_uber.type = AddrType.street 410 | if address_uber.type == 'intersection_addr': 411 | get_cl_info_street2(address) 412 | if address.street_2.street_code: 413 | address_uber.components.street_2.is_centerline_match = True 414 | 415 | create_full_names(address, address_uber.type) 416 | # if the users doesn't have the zip4 file, parser will still work 417 | if is_zip_file: 418 | get_zip_info(address, address_uber, MAX_RANGE) 419 | # if the address is an alias the zip file may or may not have the alias listed. If not, try the original 420 | if not address.mailing.zipcode and address != address_copy: 421 | get_zip_info(address_copy, address_uber_copy, MAX_RANGE) 422 | address.mailing.uspstype = address_copy.mailing.uspstype 423 | address.mailing.bldgfirm = address_copy.mailing.bldgfirm 424 | address.mailing.zip4 = address_copy.mailing.zip4 425 | address.mailing.zipcode = address_copy.mailing.zipcode 426 | address.mailing.matchdesc = address_copy.mailing.matchdesc 427 | create_full_names(address, address_uber.type) 428 | 429 | # important that full names are created before adding election 430 | if is_election_file: 431 | get_election_info(address) 432 | # if the address is an alias the zip file may or may not have the alias listed. If not, try the original 433 | if not address.election.blockid and address != address_copy: 434 | get_election_info(address_copy) 435 | address.election.blockid = address_copy.election.blockid 436 | address.election.precinct = address_copy.election.precinct 437 | if address_uber.components.address_unit.unit_type == '' and address_uber.components.address_unit.unit_num != '': 438 | address_uber.components.address_unit.unit_type = '#' 439 | 440 | if len(address.mailing.zip4) == 4 and address.mailing.zip4[2:4] == 'ND': 441 | address.mailing.zip4 = '' 442 | 443 | if address_uber.type == AddrType.intersection and address.base_address.find(' & ') == -1: 444 | address_uber.type = AddrType.address 445 | if address_uber.type == AddrType.intersection: 446 | address_uber.components.output_address = address.base_address 447 | elif address_uber.type != AddrType.opa_account and \ 448 | address_uber.type != AddrType.mapreg and \ 449 | address_uber.type != AddrType.latlon and \ 450 | address_uber.type != AddrType.stateplane and \ 451 | address_uber.type != AddrType.zipcode: 452 | if address.address_unit.unit_num != -1: 453 | address_uber.components.output_address = address.base_address + ' ' + \ 454 | address.floor.floor_type + ' ' + \ 455 | address.floor.floor_num + ' ' + \ 456 | address.address_unit.unit_type + ' ' + \ 457 | address.address_unit.unit_num 458 | else: 459 | address_uber.components.output_address = address.base_address + ' ' + \ 460 | address.floor.floor_type + ' ' + \ 461 | address.floor.floor_num + ' ' + \ 462 | address.address_unit.unit_type + ' ' 463 | 464 | address_uber.components.output_address = ' '.join(address_uber.components.output_address.split()) 465 | 466 | temp_centerline = is_centerline_name(address_uber.components.street.full) 467 | 468 | if temp_centerline.full != '0': 469 | address_uber.components.street.is_centerline_match = True 470 | 471 | if address_uber.type == AddrType.street and address.street.street_code == '': 472 | address_uber.type = AddrType.none 473 | 474 | temp_centerline = is_centerline_name(address_uber.components.street_2.full) 475 | 476 | if temp_centerline.full != '0': 477 | address_uber.components.street_2.is_centerline_match = True 478 | 479 | if address_uber.components.base_address == '': 480 | address_uber.components.base_address = None 481 | if address_uber.components.mailing.zipcode == '': 482 | address_uber.components.mailing.zipcode = None 483 | if address_uber.components.mailing.zip4 == '': 484 | address_uber.components.mailing.zip4 = None 485 | if address_uber.components.mailing.uspstype == '': 486 | address_uber.components.mailing.uspstype = None 487 | if address_uber.components.mailing.bldgfirm == '': 488 | address_uber.components.mailing.bldgfirm = None 489 | if address_uber.components.cl_addr_match == '': 490 | address_uber.components.cl_addr_match = None 491 | if address_uber.components.mailing.matchdesc == '': 492 | address_uber.components.mailing.matchdesc = None 493 | if address_uber.components.cl_responsibility == '': 494 | address_uber.components.cl_responsibility = None 495 | if address_uber.components.cl_seg_id == '': 496 | address_uber.components.cl_seg_id = None 497 | if address_uber.components.street.street_code == '': 498 | address_uber.components.street.street_code = None 499 | 500 | if address_uber.components.address.addr_suffix == '': 501 | address_uber.components.address.addr_suffix = None 502 | if address_uber.components.address.high_num_full == -1: 503 | address_uber.components.address.high_num_full = None 504 | if address_uber.components.address.addrnum_type == '': 505 | address_uber.components.address.addrnum_type = None 506 | if address_uber.components.address.fractional == '': 507 | address_uber.components.address.fractional = None 508 | if address_uber.components.address.full == '': 509 | address_uber.components.address.full = None 510 | if address_uber.components.address.high == '': 511 | address_uber.components.address.high = None 512 | if address_uber.components.address.high_num == -1: 513 | address_uber.components.address.high_num = None 514 | if address_uber.components.address.isaddr == '': 515 | address_uber.components.address.isaddr = None 516 | if address_uber.components.address.low == '': 517 | address_uber.components.address.low = None 518 | if address_uber.components.address.low_num == -1: 519 | address_uber.components.address.low_num = None 520 | if address_uber.components.address.parity == '': 521 | address_uber.components.address.parity = None 522 | 523 | if address_uber.components.street.parse_method == '': 524 | address_uber.components.street.parse_method = None 525 | if address_uber.components.street.full == '': 526 | address_uber.components.street.full = None 527 | if address_uber.components.street.name == '': 528 | address_uber.components.street.name = None 529 | if address_uber.components.street.suffix == '': 530 | address_uber.components.street.suffix = None 531 | if address_uber.components.street.predir == '': 532 | address_uber.components.street.predir = None 533 | if address_uber.components.street.postdir == '': 534 | address_uber.components.street.postdir = None 535 | 536 | if address_uber.components.street_2.parse_method == '': 537 | address_uber.components.street_2.parse_method = None 538 | if address_uber.components.street_2.full == '': 539 | address_uber.components.street_2.full = None 540 | if address_uber.components.street_2.name == '': 541 | address_uber.components.street_2.name = None 542 | if address_uber.components.street_2.suffix == '': 543 | address_uber.components.street_2.suffix = None 544 | if address_uber.components.street_2.predir == '': 545 | address_uber.components.street_2.predir = None 546 | if address_uber.components.street_2.postdir == '': 547 | address_uber.components.street_2.postdir = None 548 | if address_uber.components.street_2.street_code == '': 549 | address_uber.components.street_2.street_code = None 550 | 551 | if address_uber.components.election.blockid == '': 552 | address_uber.components.election.blockid = None 553 | if address_uber.components.election.precinct == '': 554 | address_uber.components.election.precinct = None 555 | 556 | # modeled on address_uber.components.address_unit.unit_num stuff below - Jan. 2025, MJ 557 | if address_uber.components.floor.floor_num == '': 558 | address_uber.components.floor.floor_num = None 559 | if address_uber.components.floor.floor_num == -1: 560 | address_uber.components.floor.floor_num = None 561 | if address_uber.components.floor.floor_type == '': 562 | address_uber.components.floor.floor_type = None 563 | 564 | # since there aren't set values that are valid for these fields, long strings of junk values can come through 565 | # 6252 N. 4TH ST. 19120DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBBBBBBB B 566 | if address_uber.components.address_unit.unit_num != -1 and len(address_uber.components.address_unit.unit_num) > 12: 567 | address_uber.components.address_unit.unit_num = address_uber.components.address_unit.unit_num[:12] 568 | if len(address_uber.components.address_unit.unit_type) > 12: 569 | address_uber.components.address_unit.unit_num = address_uber.components.address_unit.unit_type[:12] 570 | 571 | if address_uber.components.address_unit.unit_num == '': 572 | address_uber.components.address_unit.unit_num = None 573 | if address_uber.components.address_unit.unit_num == -1: 574 | address_uber.components.address_unit.unit_num = None 575 | if address_uber.components.address_unit.unit_type == '': 576 | address_uber.components.address_unit.unit_type = None 577 | 578 | # if the only unit designator is floor, represent it in both the address_unit and floor components, 579 | # so that the address_unit component always contains at least the one USPS unit designator present 580 | if (address_uber.components.address_unit.unit_type is None and 581 | address_uber.components.floor.floor_num is not None): 582 | floor_val = address_uber.components.floor.floor_num 583 | address_uber.components.address_unit.unit_num = floor_val 584 | address_uber.components.address_unit.unit_type = 'FL' 585 | 586 | # if a unit designator was converted from something else to a floor designator through aptstd lookup path, 587 | # make sure that floor information is copied back to floor fields 588 | if (address_uber.components.address_unit.unit_type in APTFLOOR and 589 | address_uber.components.address_unit.unit_num is not None): 590 | floor_val = address_uber.components.address_unit.unit_num 591 | floor_type = address_uber.components.address_unit.unit_type 592 | address_uber.components.floor.floor_num = floor_val 593 | address_uber.components.floor.floor_type = floor_type 594 | 595 | if address_uber.input_address == '': 596 | address_uber.input_address = None 597 | if address_uber.components.output_address == '': 598 | address_uber.components.output_address = None 599 | if address_uber.type == '': 600 | address_uber.type = None 601 | # Hack to set type back to landmark: 602 | if landmark.is_landmark: 603 | address_uber.type = AddrType.landmark 604 | 605 | return address_uber 606 | 607 | def input_cleanup(address_uber, item): 608 | # defensive, just in case you get some ridiculous input 609 | item = item[0:80] 610 | address_uber.input_address = item 611 | item = item.upper() 612 | 613 | # Make sure no junk ascii chars get through 614 | item = ILLEGAL_CHARS_RE.sub('', item) 615 | 616 | items = item.split('#') 617 | if len(items) > 2: 618 | item = "{} # {}".format(items[0], items[2]) # OLD 619 | # item = "#".join(items) # allow for more than one # within address, e.g. '1234 MARKET STREET FLOOR # 15 UNIT # 6' 620 | 621 | # get rid of trailing # 1608 South St # 622 | if len(items) == 2 and items[1] == '': 623 | item = items[0] 624 | 625 | item = item.replace(',', ' ') 626 | item = item.replace('.', ' ') 627 | item = item.replace('#', ' # ') 628 | item = item.replace('&', ' AND ') 629 | item = item.replace('/', ' AND ') 630 | item = item.replace('@', ' AND ') 631 | item = item.replace(' AT ', ' AND ') 632 | item = item.replace(' UNIT UNIT', ' UNIT ') # yes this is common 633 | item = item.replace(' LBBY LBBY', ' LBBY ') # having more than one Apte causes TypeError 634 | item = item.replace('1 AND 2', ' 1/2 ') 635 | item = item.replace(' - ', '-') 636 | item = item.replace(' -', '-') 637 | item = item.replace('- ', '-') 638 | 639 | # Remove ES, WS, NS, SS 640 | item = re.sub(' (NS|SS|ES|WS)$', '', item) 641 | 642 | item = item.replace(' OPP ', ' ') 643 | if item.startswith('OPP '): 644 | item = item[4:] 645 | 646 | if ' BLOCK ' in item or ' BLK ' in item: 647 | # Parking data 648 | item = item.replace('UNIT BLK', '1 ') 649 | item = item.replace(' BLOCK OF ', ' ') 650 | item = item.replace(' BLOCK ', ' ') 651 | item = item.replace(' BLK OF ', ' ') 652 | item = item.replace(' BLK ', ' ') 653 | address_uber.type = AddrType.block 654 | 655 | item = ' '.join(item.split()) 656 | return item 657 | 658 | def check_version(): 659 | ''' 660 | Check that the latest version tag on Github matches this version of the package. 661 | If the passyunk_automation private package is installed, do the same for that as well. 662 | ''' 663 | try: 664 | r = requests.get("https://api.github.com/repos/CityOfPhiladelphia/passyunk/git/matching-refs/tags") 665 | tags, tags_private = [], [] 666 | for ref in r.json(): 667 | if ref['object']['type'] == 'tag': 668 | string = ref['ref'] 669 | find = re.findall('(?<=refs/tags/).*', string) 670 | if find != []: 671 | s = find[0] 672 | if re.findall('\+private', s) != []: 673 | tags_private.append(s) 674 | else: 675 | tags.append(s) 676 | 677 | newest_version = version.find_newest(tags) 678 | current_version = version.Version(importlib_metadata.version('passyunk')) 679 | if current_version < newest_version: 680 | logging.warning(f''' 681 | There is a new version of the Passyunk module available with updated data. 682 | Current: {current_version.version} 683 | Newest: {newest_version.version} 684 | Run `pip install git+https://github.com/CityOfPhiladelphia/passyunk` to upgrade 685 | ''') 686 | else: 687 | print(f'Current Passyunk Version: {current_version} is up-to-date') 688 | except Exception as e: 689 | logging.warning(f'Error when attempting to check public module version\nError Text: {e}') 690 | 691 | if private_installed: 692 | newest_version_private = version.find_newest(tags_private) 693 | try: 694 | current_version_private = version.Version(importlib_metadata.version('passyunk_automation')) 695 | if current_version_private < newest_version_private: 696 | logging.warning(f''' 697 | There is a new version of the private Passyunk module available with updated data. 698 | Current: {current_version_private.version} 699 | Newest: {newest_version_private.version} 700 | Run `pip install git+ssh://git@github.com/CityOfPhiladelphia/passyunk_automation.git` 701 | from the same environment that the public passyunk module was installed in. 702 | ''') 703 | else: 704 | print(f'Current Passyunk Private Data Version: {current_version_private} is up-to-date') 705 | except Exception as e: 706 | logging.warning(f'Error when attempting to check private module version\nError Text: {e}') 707 | 708 | ''' 709 | RUN 710 | ''' 711 | 712 | cwd = os.path.dirname(__file__) 713 | cwd += '/pdata' 714 | 715 | street_centerline_lookup, street_centerline_name_lookup, cl_name_lookup, cl_pre_lookup, \ 716 | cl_suffix_lookup = create_centerline_street_lookup() 717 | 718 | # if the user doesn't have the zip4 or centerline file, parser will still work 719 | def warn_file(name: str, found: bool): 720 | if not found: 721 | logging.warning(f'{name} file not found') 722 | 723 | is_cl_file = create_cl_lookup() 724 | is_al_file = create_al_lookup() 725 | if private_installed: 726 | is_zip_file = create_zip4_lookup() 727 | is_election_file = create_election_lookup() 728 | else: 729 | is_zip_file, is_election_file = False, False 730 | warn_file('Centerline', is_cl_file) 731 | warn_file('Alias', is_al_file) 732 | warn_file('USPS', is_zip_file) 733 | warn_file('Election', is_election_file) 734 | 735 | check_version() 736 | 737 | class PassyunkParser: 738 | def __init__(self, return_dict=True, MAX_RANGE=200): 739 | self.return_dict = return_dict 740 | self.MAX_RANGE = MAX_RANGE 741 | self.zip_file_loaded = True if is_zip_file else False 742 | self.cl_file_loaded = True if is_cl_file else False 743 | self.election_file_loaded = True if is_election_file else False 744 | 745 | def parse(self, addr_str): 746 | parsed_out = parse(addr_str, self.MAX_RANGE) 747 | 748 | if self.return_dict: 749 | # Hack to make nested addrnum a dict as well 750 | parsed_out.components.mailing = parsed_out.components.mailing.__dict__ 751 | parsed_out.components.election = parsed_out.components.election.__dict__ 752 | parsed_out.components.address = parsed_out.components.address.__dict__ 753 | parsed_out.components.street = parsed_out.components.street.__dict__ 754 | parsed_out.components.street_2 = parsed_out.components.street_2.__dict__ 755 | parsed_out.components.floor = parsed_out.components.floor.__dict__ 756 | parsed_out.components.address_unit = parsed_out.components.address_unit.__dict__ 757 | parsed_out.components = parsed_out.components.__dict__ 758 | return parsed_out.__dict__ 759 | 760 | return parsed_out 761 | -------------------------------------------------------------------------------- /passyunk/pdata/alias_streets.csv: -------------------------------------------------------------------------------- 1 | N 5TH ST,N,5TH,ST, 2 | N 5TH ST,N,5TH,ST, 3 | N 5TH ST,N,5TH,ST, 4 | N 5TH ST,N,5TH,ST, 5 | S 5TH ST,S,5TH,ST, 6 | S 5TH ST,S,5TH,ST, 7 | S 5TH ST,S,5TH,ST, 8 | S 5TH ST,S,5TH,ST, 9 | N 6TH ST,N,6TH,ST, 10 | N 6TH ST,N,6TH,ST, 11 | S 6TH ST,S,6TH,ST, 12 | S 6TH ST,S,6TH,ST, 13 | S 6TH ST,S,6TH,ST, 14 | S 6TH ST,S,6TH,ST, 15 | 291 HWY,,291,HWY, 16 | 291 HWY,,291,HWY, 17 | 291 HWY,,291,HWY, 18 | 291 HWY,,291,HWY, 19 | 291 HWY,,291,HWY, 20 | 291 HWY,,291,HWY, 21 | 291 HWY,,291,HWY, 22 | 291 HWY,,291,HWY, 23 | 291 HWY,,291,HWY, 24 | 291 HWY,,291,HWY, 25 | 291 HWY,,291,HWY, 26 | 291 HWY,,291,HWY, 27 | 291 HWY,,291,HWY, 28 | 291 HWY,,291,HWY, 29 | 291 HWY,,291,HWY, 30 | 291 HWY,,291,HWY, 31 | 291 HWY,,291,HWY, 32 | 291,,291,, 33 | 291,,291,, 34 | 291,,291,, 35 | 291,,291,, 36 | 291,,291,, 37 | 291,,291,, 38 | 291,,291,, 39 | 291,,291,, 40 | 291,,291,, 41 | 291,,291,, 42 | 291,,291,, 43 | 291,,291,, 44 | 291,,291,, 45 | 291,,291,, 46 | 291,,291,, 47 | 291,,291,, 48 | 291,,291,, 49 | 291,,291,, 50 | 291,,291,, 51 | 291,,291,, 52 | 291,,291,, 53 | S 58TH ST,S,58TH,ST, 54 | S 58TH ST,S,58TH,ST, 55 | S 58TH ST,S,58TH,ST, 56 | S 58TH ST,S,58TH,ST, 57 | N 5TH ST,N,5TH,ST, 58 | N 5TH ST,N,5TH,ST, 59 | N 5TH ST,N,5TH,ST, 60 | N 5TH ST,N,5TH,ST, 61 | S 5TH ST,S,5TH,ST, 62 | S 5TH ST,S,5TH,ST, 63 | S 5TH ST,S,5TH,ST, 64 | S 5TH ST,S,5TH,ST, 65 | N 6TH ST,N,6TH,ST, 66 | N 6TH ST,N,6TH,ST, 67 | S 6TH ST,S,6TH,ST, 68 | S 6TH ST,S,6TH,ST, 69 | S 6TH ST,S,6TH,ST, 70 | S 6TH ST,S,6TH,ST, 71 | I 95 RAMP,I,95 RAMP,, 72 | I 95 RAMP,I,95 RAMP,, 73 | I 95 RAMP,I,95 RAMP,, 74 | I 95 RAMP,I,95 RAMP,, 75 | I 95 RAMP,I,95 RAMP,, 76 | I 95 RAMP,I,95 RAMP,, 77 | I 95 RAMP,I,95 RAMP,, 78 | I 95 RAMP,I,95 RAMP,, 79 | I 95 RAMP,I,95 RAMP,, 80 | I 95 RAMP,I,95 RAMP,, 81 | I 95 RAMP,I,95 RAMP,, 82 | I 95 RAMP,I,95 RAMP,, 83 | I 95 RAMP,I,95 RAMP,, 84 | I 95 RAMP,I,95 RAMP,, 85 | I 95 RAMP,I,95 RAMP,, 86 | I 95 RAMP,I,95 RAMP,, 87 | I 95 RAMP,I,95 RAMP,, 88 | I 95 RAMP,I,95 RAMP,, 89 | I 95 RAMP,I,95 RAMP,, 90 | I 95 RAMP,I,95 RAMP,, 91 | I 95 RAMP,I,95 RAMP,, 92 | I 95 RAMP,I,95 RAMP,, 93 | I 95 RAMP,I,95 RAMP,, 94 | I 95 RAMP,I,95 RAMP,, 95 | I 95 RAMP,I,95 RAMP,, 96 | I 95 RAMP,I,95 RAMP,, 97 | I 95 RAMP,I,95 RAMP,, 98 | I 95 RAMP,I,95 RAMP,, 99 | I 95 RAMP,I,95 RAMP,, 100 | I 95 RAMP,I,95 RAMP,, 101 | I 95 RAMP,I,95 RAMP,, 102 | I 95 RAMP,I,95 RAMP,, 103 | I 95 RAMP,I,95 RAMP,, 104 | I 95 RAMP,I,95 RAMP,, 105 | I 95 RAMP,I,95 RAMP,, 106 | I 95 RAMP,I,95 RAMP,, 107 | I 95 RAMP,I,95 RAMP,, 108 | I 95 RAMP,I,95 RAMP,, 109 | ASHLAND ST,,ASHLAND,ST, 110 | ASHLAND ST,,ASHLAND,ST, 111 | AYRDALE CIR,,AYRDALE,CIR, 112 | AYRDALE CIR,,AYRDALE,CIR, 113 | BOATHOUSE ROW,,BOATHOUSE,ROW, 114 | BOATHOUSE ROW,,BOATHOUSE,ROW, 115 | BOATHOUSE ROW,,BOATHOUSE,ROW, 116 | BOATHOUSE ROW,,BOATHOUSE,ROW, 117 | BOATHOUSE ROW,,BOATHOUSE,ROW, 118 | BOATHOUSE ROW,,BOATHOUSE,ROW, 119 | BOATHOUSE ROW,,BOATHOUSE,ROW, 120 | BOATHOUSE ROW,,BOATHOUSE,ROW, 121 | BOATHOUSE ROW,,BOATHOUSE,ROW, 122 | BOATHOUSE ROW,,BOATHOUSE,ROW, 123 | BOATHOUSE ROW,,BOATHOUSE,ROW, 124 | BOATHOUSE ROW,,BOATHOUSE,ROW, 125 | BOATHOUSE ROW,,BOATHOUSE,ROW, 126 | BOATHOUSE ROW,,BOATHOUSE,ROW, 127 | BOATHOUSE ROW,,BOATHOUSE,ROW, 128 | BOATHOUSE ROW,,BOATHOUSE,ROW, 129 | BOATHOUSE ROW,,BOATHOUSE,ROW, 130 | BOATHOUSE ROW,,BOATHOUSE,ROW, 131 | BOATHOUSE ROW,,BOATHOUSE,ROW, 132 | BOATHOUSE ROW,,BOATHOUSE,ROW, 133 | BOATHOUSE ROW,,BOATHOUSE,ROW, 134 | BOATHOUSE ROW,,BOATHOUSE,ROW, 135 | BOATHOUSE ROW,,BOATHOUSE,ROW, 136 | BOATHOUSE ROW,,BOATHOUSE,ROW, 137 | BOATHOUSE ROW,,BOATHOUSE,ROW, 138 | CAMBRIDGE ST,,CAMBRIDGE,ST, 139 | CARWITHAN ST,,CARWITHAN,ST, 140 | CARWITHAN ST,,CARWITHAN,ST, 141 | CITY LINE AVE,,CITY LINE,AVE, 142 | CITY LINE AVE,,CITY LINE,AVE, 143 | CITY LINE AVE,,CITY LINE,AVE, 144 | CITY LINE AVE,,CITY LINE,AVE, 145 | CITY LINE AVE,,CITY LINE,AVE, 146 | CITY LINE AVE,,CITY LINE,AVE, 147 | CITY LINE AVE,,CITY LINE,AVE, 148 | CITY LINE AVE,,CITY LINE,AVE, 149 | CITY LINE AVE,,CITY LINE,AVE, 150 | CITY LINE AVE,,CITY LINE,AVE, 151 | CITY LINE AVE,,CITY LINE,AVE, 152 | CITY LINE AVE,,CITY LINE,AVE, 153 | CITY LINE AVE,,CITY LINE,AVE, 154 | CITY LINE AVE,,CITY LINE,AVE, 155 | CITY LINE AVE,,CITY LINE,AVE, 156 | CITY LINE AVE,,CITY LINE,AVE, 157 | CITY LINE AVE,,CITY LINE,AVE, 158 | CITY LINE AVE,,CITY LINE,AVE, 159 | CITY LINE AVE,,CITY LINE,AVE, 160 | CITY LINE AVE,,CITY LINE,AVE, 161 | CITY LINE AVE,,CITY LINE,AVE, 162 | CITY LINE AVE,,CITY LINE,AVE, 163 | CITY LINE AVE,,CITY LINE,AVE, 164 | CITY LINE AVE,,CITY LINE,AVE, 165 | CITY LINE AVE,,CITY LINE,AVE, 166 | CITY LINE AVE,,CITY LINE,AVE, 167 | CITY LINE AVE,,CITY LINE,AVE, 168 | CITY LINE AVE,,CITY LINE,AVE, 169 | CITY LINE AVE,,CITY LINE,AVE, 170 | CITY LINE AVE,,CITY LINE,AVE, 171 | CITY LINE AVE,,CITY LINE,AVE, 172 | CITY LINE AVE,,CITY LINE,AVE, 173 | CITY LINE AVE,,CITY LINE,AVE, 174 | CITY LINE AVE,,CITY LINE,AVE, 175 | CITY LINE AVE,,CITY LINE,AVE, 176 | CITY LINE AVE,,CITY LINE,AVE, 177 | CITY LINE AVE,,CITY LINE,AVE, 178 | CITY LINE AVE,,CITY LINE,AVE, 179 | CITY LINE AVE,,CITY LINE,AVE, 180 | CITY LINE AVE,,CITY LINE,AVE, 181 | CITY LINE AVE,,CITY LINE,AVE, 182 | CITY LINE AVE,,CITY LINE,AVE, 183 | CITY LINE AVE,,CITY LINE,AVE, 184 | CITY LINE AVE,,CITY LINE,AVE, 185 | CITY LINE AVE,,CITY LINE,AVE, 186 | CITY LINE AVE,,CITY LINE,AVE, 187 | CITY LINE AVE,,CITY LINE,AVE, 188 | CITY LINE AVE,,CITY LINE,AVE, 189 | CITY LINE AVE,,CITY LINE,AVE, 190 | N CONCOURSE DR,N,CONCOURSE,DR, 191 | CULVERT ST,,CULVERT,ST, 192 | CULVERT ST,,CULVERT,ST, 193 | CULVERT ST,,CULVERT,ST, 194 | N DELAWARE AVE,N,DELAWARE,AVE, 195 | N DELAWARE AVE,N,DELAWARE,AVE, 196 | N DELAWARE AVE,N,DELAWARE,AVE, 197 | N DELAWARE AVE,N,DELAWARE,AVE, 198 | N DELAWARE AVE,N,DELAWARE,AVE, 199 | N DELAWARE AVE,N,DELAWARE,AVE, 200 | N DELAWARE AVE,N,DELAWARE,AVE, 201 | S DELAWARE AVE,S,DELAWARE,AVE, 202 | S DELAWARE AVE,S,DELAWARE,AVE, 203 | S DELAWARE AVE,S,DELAWARE,AVE, 204 | S DELAWARE AVE,S,DELAWARE,AVE, 205 | S DELAWARE AVE,S,DELAWARE,AVE, 206 | S DELAWARE AVE,S,DELAWARE,AVE, 207 | S DELAWARE AVE,S,DELAWARE,AVE, 208 | S DELAWARE AVE,S,DELAWARE,AVE, 209 | S DELAWARE AVE,S,DELAWARE,AVE, 210 | S DELAWARE AVE,S,DELAWARE,AVE, 211 | S DELAWARE AVE,S,DELAWARE,AVE, 212 | S DELAWARE AVE,S,DELAWARE,AVE, 213 | S DELAWARE AVE,S,DELAWARE,AVE, 214 | S DELAWARE AVE,S,DELAWARE,AVE, 215 | S DELAWARE AVE,S,DELAWARE,AVE, 216 | S DELAWARE AVE,S,DELAWARE,AVE, 217 | S DELAWARE AVE,S,DELAWARE,AVE, 218 | S DELAWARE AVE,S,DELAWARE,AVE, 219 | S DELAWARE AVE,S,DELAWARE,AVE, 220 | S DELAWARE AVE,S,DELAWARE,AVE, 221 | S DELAWARE AVE,S,DELAWARE,AVE, 222 | S DELAWARE AVE,S,DELAWARE,AVE, 223 | S DELAWARE AVE,S,DELAWARE,AVE, 224 | S DELAWARE AVE,S,DELAWARE,AVE, 225 | S DELAWARE AVE,S,DELAWARE,AVE, 226 | S DELAWARE AVE,S,DELAWARE,AVE, 227 | S DELAWARE AVE,S,DELAWARE,AVE, 228 | ERNEST ST,,ERNEST,ST, 229 | ERNEST ST,,ERNEST,ST, 230 | ERNEST ST,,ERNEST,ST, 231 | ERNEST ST,,ERNEST,ST, 232 | ERNEST ST,,ERNEST,ST, 233 | ERNEST ST,,ERNEST,ST, 234 | ERNEST WAY,,ERNEST,WAY, 235 | EARNST ST,,EARNST,ST, 236 | ERNST ST,,ERNST,ST, 237 | FAIRMOUNT BIKEWAY TR,,FAIRMOUNT BIKEWAY,TR, 238 | FAIRMOUNT BIKEWAY TR,,FAIRMOUNT BIKEWAY,TR, 239 | FARRAGUT ST,,FARRAGUT,ST, 240 | FARRAGUT ST,,FARRAGUT,ST, 241 | FOURTYFIRST ST DR,,FOURTYFIRST ST,DR, 242 | FOURTYFIRST ST DR,,FOURTYFIRST ST,DR, 243 | FOURTYFIRST ST DR,,FOURTYFIRST ST,DR, 244 | FOURTYFIRST ST DR,,FOURTYFIRST ST,DR, 245 | W GLENWOOD AVE,W,GLENWOOD,AVE, 246 | W GLENWOOD AVE,W,GLENWOOD,AVE, 247 | W GLENWOOD AVE,W,GLENWOOD,AVE, 248 | W GOWEN AVE,W,GOWEN,AVE, 249 | GRAYS FERRY TR,,GRAYS FERRY,TR, 250 | GRAYS FERRY TR,,GRAYS FERRY,TR, 251 | GRAYS FERRY TR,,GRAYS FERRY,TR, 252 | HADDINGTON LN,,HADDINGTON,LN, 253 | HADDINGTON LN,,HADDINGTON,LN, 254 | HADDINGTON LN,,HADDINGTON,LN, 255 | HADDINGTON LN,,HADDINGTON,LN, 256 | HADDINGTON LN,,HADDINGTON,LN, 257 | HADDINGTON ST,,HADDINGTON,ST, 258 | HADDINGTON ST,,HADDINGTON,ST, 259 | HADDINGTON ST,,HADDINGTON,ST, 260 | HADDINGTON ST,,HADDINGTON,ST, 261 | HADDINGTON ST,,HADDINGTON,ST, 262 | HADDINGTON ST,,HADDINGTON,ST, 263 | HADDINGTON ST,,HADDINGTON,ST, 264 | HADDINGTON ST,,HADDINGTON,ST, 265 | HADDINGTON ST,,HADDINGTON,ST, 266 | HARLEY AVE,,HARLEY,AVE, 267 | HARLEY AVE,,HARLEY,AVE, 268 | HARLEY AVE,,HARLEY,AVE, 269 | HARLEY AVE,,HARLEY,AVE, 270 | HARLEY AVE,,HARLEY,AVE, 271 | HARLEY AVE,,HARLEY,AVE, 272 | HARLEY AVE,,HARLEY,AVE, 273 | HARLEY AVE,,HARLEY,AVE, 274 | HARLEY AVE,,HARLEY,AVE, 275 | HARLEY DR,,HARLEY,DR, 276 | HARLEY DR,,HARLEY,DR, 277 | HARLEY DR,,HARLEY,DR, 278 | HARLEY DR,,HARLEY,DR, 279 | HARLEY DR,,HARLEY,DR, 280 | HARLEY DR,,HARLEY,DR, 281 | HARLEY DR,,HARLEY,DR, 282 | HARLEY DR,,HARLEY,DR, 283 | HARLEY DR,,HARLEY,DR, 284 | HARLEY DR,,HARLEY,DR, 285 | HARLEY DR,,HARLEY,DR, 286 | HARLEY DR,,HARLEY,DR, 287 | HARLEY DR,,HARLEY,DR, 288 | HARLEY DR,,HARLEY,DR, 289 | HARLEY PL,,HARLEY,PL, 290 | HARLEY PL,,HARLEY,PL, 291 | HARLEY PL,,HARLEY,PL, 292 | HARLEY PL,,HARLEY,PL, 293 | HARLEY PL,,HARLEY,PL, 294 | HARLEY PL,,HARLEY,PL, 295 | HARLEY PL,,HARLEY,PL, 296 | HARLEY PL,,HARLEY,PL, 297 | HARLEY PL,,HARLEY,PL, 298 | HARLEY PL,,HARLEY,PL, 299 | HARLEY PL,,HARLEY,PL, 300 | HARLEY PL,,HARLEY,PL, 301 | HARLEY PL,,HARLEY,PL, 302 | HARLEY PL,,HARLEY,PL, 303 | HARLEY ST,,HARLEY,ST, 304 | HARLEY ST,,HARLEY,ST, 305 | HARLEY ST,,HARLEY,ST, 306 | HARLEY ST,,HARLEY,ST, 307 | HARLEY ST,,HARLEY,ST, 308 | HARLEY ST,,HARLEY,ST, 309 | HARLEY ST,,HARLEY,ST, 310 | HARLEY ST,,HARLEY,ST, 311 | HARLEY ST,,HARLEY,ST, 312 | HARLEY ST,,HARLEY,ST, 313 | HARLEY TER,,HARLEY,TER, 314 | HARLEY TER,,HARLEY,TER, 315 | HARLEY TER,,HARLEY,TER, 316 | HARLEY TER,,HARLEY,TER, 317 | HARLEY TER,,HARLEY,TER, 318 | HARLEY TER,,HARLEY,TER, 319 | HARLEY TER,,HARLEY,TER, 320 | HARLEY TER,,HARLEY,TER, 321 | HARLEY TER,,HARLEY,TER, 322 | HARLEY TER,,HARLEY,TER, 323 | HARLEY TER,,HARLEY,TER, 324 | HARLEY TER,,HARLEY,TER, 325 | HARLEY TER,,HARLEY,TER, 326 | HOLLINGSWORTH ST,,HOLLINGSWORTH,ST, 327 | HOLLINGSWORTH ST,,HOLLINGSWORTH,ST, 328 | I291,,I291,, 329 | I291,,I291,, 330 | I291,,I291,, 331 | I291,,I291,, 332 | I291,,I291,, 333 | I291,,I291,, 334 | I291,,I291,, 335 | I291,,I291,, 336 | I291,,I291,, 337 | I291,,I291,, 338 | I291,,I291,, 339 | I291,,I291,, 340 | I291,,I291,, 341 | I291,,I291,, 342 | I291,,I291,, 343 | I291,,I291,, 344 | I291,,I291,, 345 | I291,,I291,, 346 | I291,,I291,, 347 | I291,,I291,, 348 | I291,,I291,, 349 | I676 EXP,,I676,EXP, 350 | I676 EXP,,I676,EXP, 351 | I676 EXP,,I676,EXP, 352 | I676 EXP,,I676,EXP, 353 | I676 EXP,,I676,EXP, 354 | I676 EXP,,I676,EXP, 355 | I676 EXP,,I676,EXP, 356 | I676 EXP,,I676,EXP, 357 | I676 EXP,,I676,EXP, 358 | I676 EXP,,I676,EXP, 359 | I676 EXP,,I676,EXP, 360 | I676 EXP,,I676,EXP, 361 | I676 EXP,,I676,EXP, 362 | I676 RMP,,I676,RMP, 363 | I676 RMP,,I676,RMP, 364 | I676,,I676,, 365 | I676,,I676,, 366 | I676,,I676,, 367 | I676,,I676,, 368 | I676,,I676,, 369 | I676,,I676,, 370 | I676,,I676,, 371 | I676,,I676,, 372 | I676,,I676,, 373 | I676,,I676,, 374 | I676,,I676,, 375 | I676,,I676,, 376 | I676,,I676,, 377 | I 676 EXP,,I 676,EXP, 378 | I 676 EXP,,I 676,EXP, 379 | I 676 EXP,,I 676,EXP, 380 | I 676 EXP,,I 676,EXP, 381 | I 676 EXP,,I 676,EXP, 382 | I 676 EXP,,I 676,EXP, 383 | I 676 EXP,,I 676,EXP, 384 | I 676 EXP,,I 676,EXP, 385 | I 676 EXP,,I 676,EXP, 386 | I 676 EXP,,I 676,EXP, 387 | I 676 EXP,,I 676,EXP, 388 | I 676 EXP,,I 676,EXP, 389 | I 676 EXP,,I 676,EXP, 390 | I 676 RMP,,I 676,RMP, 391 | I 676 RMP,,I 676,RMP, 392 | I 676,,I 676,, 393 | I 676,,I 676,, 394 | I 676,,I 676,, 395 | I 676,,I 676,, 396 | I 676,,I 676,, 397 | I 676,,I 676,, 398 | I 676,,I 676,, 399 | I 676,,I 676,, 400 | I 676,,I 676,, 401 | I 676,,I 676,, 402 | I 676,,I 676,, 403 | I 676,,I 676,, 404 | I 676,,I 676,, 405 | EB I76,EB,I76,, 406 | EB I76,EB,I76,, 407 | EB I76,EB,I76,, 408 | EB I76,EB,I76,, 409 | EB I76,EB,I76,, 410 | EB I76,EB,I76,, 411 | EB I76,EB,I76,, 412 | EB I76,EB,I76,, 413 | EB I76,EB,I76,, 414 | EB I76,EB,I76,, 415 | EB I76,EB,I76,, 416 | EB I76,EB,I76,, 417 | EB I76,EB,I76,, 418 | EB I76,EB,I76,, 419 | EB I76,EB,I76,, 420 | EB I76,EB,I76,, 421 | EB I76,EB,I76,, 422 | EB I76,EB,I76,, 423 | EB I76,EB,I76,, 424 | EB I76,EB,I76,, 425 | EB I76,EB,I76,, 426 | EB I76,EB,I76,, 427 | EB I76,EB,I76,, 428 | EB I76,EB,I76,, 429 | EB I76,EB,I76,, 430 | EB I76,EB,I76,, 431 | EB I76,EB,I76,, 432 | EB I76,EB,I76,, 433 | EB I76,EB,I76,, 434 | EB I76,EB,I76,, 435 | EB I76,EB,I76,, 436 | EB I76,EB,I76,, 437 | EB I76,EB,I76,, 438 | EB I76,EB,I76,, 439 | EB I76,EB,I76,, 440 | WB I76,WB,I76,, 441 | WB I76,WB,I76,, 442 | WB I76,WB,I76,, 443 | WB I76,WB,I76,, 444 | WB I76,WB,I76,, 445 | WB I76,WB,I76,, 446 | WB I76,WB,I76,, 447 | WB I76,WB,I76,, 448 | WB I76,WB,I76,, 449 | WB I76,WB,I76,, 450 | WB I76,WB,I76,, 451 | WB I76,WB,I76,, 452 | WB I76,WB,I76,, 453 | WB I76,WB,I76,, 454 | WB I76,WB,I76,, 455 | WB I76,WB,I76,, 456 | WB I76,WB,I76,, 457 | WB I76,WB,I76,, 458 | WB I76,WB,I76,, 459 | WB I76,WB,I76,, 460 | WB I76,WB,I76,, 461 | WB I76,WB,I76,, 462 | WB I76,WB,I76,, 463 | WB I76,WB,I76,, 464 | WB I76,WB,I76,, 465 | WB I76,WB,I76,, 466 | WB I76,WB,I76,, 467 | WB I76,WB,I76,, 468 | WB I76,WB,I76,, 469 | WB I76,WB,I76,, 470 | WB I76,WB,I76,, 471 | WB I76,WB,I76,, 472 | WB I76,WB,I76,, 473 | WB I76,WB,I76,, 474 | WB I76,WB,I76,, 475 | I76 ST,,I76,ST, 476 | I76 ST,,I76,ST, 477 | I76 ST,,I76,ST, 478 | I76 ST,,I76,ST, 479 | I76 ST,,I76,ST, 480 | I76 ST,,I76,ST, 481 | I76 ST,,I76,ST, 482 | I76 ST,,I76,ST, 483 | I76 ST,,I76,ST, 484 | I76 ST,,I76,ST, 485 | I76 ST,,I76,ST, 486 | I76 ST,,I76,ST, 487 | I76 ST,,I76,ST, 488 | I76 ST,,I76,ST, 489 | I76 ST,,I76,ST, 490 | I76 ST,,I76,ST, 491 | I76 ST,,I76,ST, 492 | I76 ST,,I76,ST, 493 | I76 ST,,I76,ST, 494 | I76 ST,,I76,ST, 495 | I76 ST,,I76,ST, 496 | I76 ST,,I76,ST, 497 | I76 ST,,I76,ST, 498 | I76 ST,,I76,ST, 499 | I76 ST,,I76,ST, 500 | I76 ST,,I76,ST, 501 | I76 ST,,I76,ST, 502 | I76 ST,,I76,ST, 503 | I76 ST,,I76,ST, 504 | I76 ST,,I76,ST, 505 | I76 ST,,I76,ST, 506 | I76 ST,,I76,ST, 507 | I76 ST,,I76,ST, 508 | I76 ST,,I76,ST, 509 | I76 ST,,I76,ST, 510 | I76 ST,,I76,ST, 511 | I76 ST,,I76,ST, 512 | I76 ST,,I76,ST, 513 | I76 ST,,I76,ST, 514 | I76 ST,,I76,ST, 515 | I76 ST,,I76,ST, 516 | I76 ST,,I76,ST, 517 | I76 ST,,I76,ST, 518 | I76 ST,,I76,ST, 519 | I76 ST,,I76,ST, 520 | I76 ST,,I76,ST, 521 | I76 ST,,I76,ST, 522 | I76 ST,,I76,ST, 523 | I76 ST,,I76,ST, 524 | I76 ST,,I76,ST, 525 | I76 ST,,I76,ST, 526 | I76 ST,,I76,ST, 527 | I76 ST,,I76,ST, 528 | I76 ST,,I76,ST, 529 | I76 ST,,I76,ST, 530 | I76 ST,,I76,ST, 531 | I76 ST,,I76,ST, 532 | I76 ST,,I76,ST, 533 | I76 ST,,I76,ST, 534 | I76 ST,,I76,ST, 535 | I76 ST,,I76,ST, 536 | I76 ST,,I76,ST, 537 | I76 ST,,I76,ST, 538 | I76 ST,,I76,ST, 539 | I76 ST,,I76,ST, 540 | I76 ST,,I76,ST, 541 | I76 ST,,I76,ST, 542 | I76 ST,,I76,ST, 543 | I76 ST,,I76,ST, 544 | I76 ST,,I76,ST, 545 | I76 ST,,I76,ST, 546 | I76,,I76,, 547 | I76,,I76,, 548 | I76,,I76,, 549 | I76,,I76,, 550 | I76,,I76,, 551 | I76,,I76,, 552 | I76,,I76,, 553 | I76,,I76,, 554 | I76,,I76,, 555 | I76,,I76,, 556 | I76,,I76,, 557 | I76,,I76,, 558 | I76,,I76,, 559 | I76,,I76,, 560 | I76,,I76,, 561 | I76,,I76,, 562 | I76,,I76,, 563 | I76,,I76,, 564 | I76,,I76,, 565 | I76,,I76,, 566 | I76,,I76,, 567 | I76,,I76,, 568 | I76,,I76,, 569 | I76,,I76,, 570 | I76,,I76,, 571 | I76,,I76,, 572 | I76,,I76,, 573 | I76,,I76,, 574 | I76,,I76,, 575 | I76,,I76,, 576 | I76,,I76,, 577 | I76,,I76,, 578 | I76,,I76,, 579 | I76,,I76,, 580 | I76,,I76,, 581 | I76,,I76,, 582 | I76,,I76,, 583 | I76,,I76,, 584 | I76,,I76,, 585 | I76,,I76,, 586 | I76,,I76,, 587 | I76,,I76,, 588 | I76,,I76,, 589 | I76,,I76,, 590 | I76,,I76,, 591 | I76,,I76,, 592 | I76,,I76,, 593 | I76,,I76,, 594 | I76,,I76,, 595 | I76,,I76,, 596 | I76,,I76,, 597 | I76,,I76,, 598 | I76,,I76,, 599 | I76,,I76,, 600 | I76,,I76,, 601 | I76,,I76,, 602 | I76,,I76,, 603 | I76,,I76,, 604 | I76,,I76,, 605 | I76,,I76,, 606 | I76,,I76,, 607 | I76,,I76,, 608 | I76,,I76,, 609 | I76,,I76,, 610 | I76,,I76,, 611 | I76,,I76,, 612 | I76,,I76,, 613 | I76,,I76,, 614 | I76,,I76,, 615 | I76,,I76,, 616 | I76,,I76,, 617 | EB I 76,EB,I 76,, 618 | EB I 76,EB,I 76,, 619 | EB I 76,EB,I 76,, 620 | EB I 76,EB,I 76,, 621 | EB I 76,EB,I 76,, 622 | EB I 76,EB,I 76,, 623 | EB I 76,EB,I 76,, 624 | EB I 76,EB,I 76,, 625 | EB I 76,EB,I 76,, 626 | EB I 76,EB,I 76,, 627 | EB I 76,EB,I 76,, 628 | EB I 76,EB,I 76,, 629 | EB I 76,EB,I 76,, 630 | EB I 76,EB,I 76,, 631 | EB I 76,EB,I 76,, 632 | EB I 76,EB,I 76,, 633 | EB I 76,EB,I 76,, 634 | EB I 76,EB,I 76,, 635 | EB I 76,EB,I 76,, 636 | EB I 76,EB,I 76,, 637 | EB I 76,EB,I 76,, 638 | EB I 76,EB,I 76,, 639 | EB I 76,EB,I 76,, 640 | EB I 76,EB,I 76,, 641 | EB I 76,EB,I 76,, 642 | EB I 76,EB,I 76,, 643 | EB I 76,EB,I 76,, 644 | EB I 76,EB,I 76,, 645 | EB I 76,EB,I 76,, 646 | EB I 76,EB,I 76,, 647 | EB I 76,EB,I 76,, 648 | EB I 76,EB,I 76,, 649 | EB I 76,EB,I 76,, 650 | EB I 76,EB,I 76,, 651 | EB I 76,EB,I 76,, 652 | WB I 76,WB,I 76,, 653 | WB I 76,WB,I 76,, 654 | WB I 76,WB,I 76,, 655 | WB I 76,WB,I 76,, 656 | WB I 76,WB,I 76,, 657 | WB I 76,WB,I 76,, 658 | WB I 76,WB,I 76,, 659 | WB I 76,WB,I 76,, 660 | WB I 76,WB,I 76,, 661 | WB I 76,WB,I 76,, 662 | WB I 76,WB,I 76,, 663 | WB I 76,WB,I 76,, 664 | WB I 76,WB,I 76,, 665 | WB I 76,WB,I 76,, 666 | WB I 76,WB,I 76,, 667 | WB I 76,WB,I 76,, 668 | WB I 76,WB,I 76,, 669 | WB I 76,WB,I 76,, 670 | WB I 76,WB,I 76,, 671 | WB I 76,WB,I 76,, 672 | WB I 76,WB,I 76,, 673 | WB I 76,WB,I 76,, 674 | WB I 76,WB,I 76,, 675 | WB I 76,WB,I 76,, 676 | WB I 76,WB,I 76,, 677 | WB I 76,WB,I 76,, 678 | WB I 76,WB,I 76,, 679 | WB I 76,WB,I 76,, 680 | WB I 76,WB,I 76,, 681 | WB I 76,WB,I 76,, 682 | WB I 76,WB,I 76,, 683 | WB I 76,WB,I 76,, 684 | WB I 76,WB,I 76,, 685 | WB I 76,WB,I 76,, 686 | WB I 76,WB,I 76,, 687 | I 76 ST,,I 76,ST, 688 | I 76 ST,,I 76,ST, 689 | I 76 ST,,I 76,ST, 690 | I 76 ST,,I 76,ST, 691 | I 76 ST,,I 76,ST, 692 | I 76 ST,,I 76,ST, 693 | I 76 ST,,I 76,ST, 694 | I 76 ST,,I 76,ST, 695 | I 76 ST,,I 76,ST, 696 | I 76 ST,,I 76,ST, 697 | I 76 ST,,I 76,ST, 698 | I 76 ST,,I 76,ST, 699 | I 76 ST,,I 76,ST, 700 | I 76 ST,,I 76,ST, 701 | I 76 ST,,I 76,ST, 702 | I 76 ST,,I 76,ST, 703 | I 76 ST,,I 76,ST, 704 | I 76 ST,,I 76,ST, 705 | I 76 ST,,I 76,ST, 706 | I 76 ST,,I 76,ST, 707 | I 76 ST,,I 76,ST, 708 | I 76 ST,,I 76,ST, 709 | I 76 ST,,I 76,ST, 710 | I 76 ST,,I 76,ST, 711 | I 76 ST,,I 76,ST, 712 | I 76 ST,,I 76,ST, 713 | I 76 ST,,I 76,ST, 714 | I 76 ST,,I 76,ST, 715 | I 76 ST,,I 76,ST, 716 | I 76 ST,,I 76,ST, 717 | I 76 ST,,I 76,ST, 718 | I 76 ST,,I 76,ST, 719 | I 76 ST,,I 76,ST, 720 | I 76 ST,,I 76,ST, 721 | I 76 ST,,I 76,ST, 722 | I 76 ST,,I 76,ST, 723 | I 76 ST,,I 76,ST, 724 | I 76 ST,,I 76,ST, 725 | I 76 ST,,I 76,ST, 726 | I 76 ST,,I 76,ST, 727 | I 76 ST,,I 76,ST, 728 | I 76 ST,,I 76,ST, 729 | I 76 ST,,I 76,ST, 730 | I 76 ST,,I 76,ST, 731 | I 76 ST,,I 76,ST, 732 | I 76 ST,,I 76,ST, 733 | I 76 ST,,I 76,ST, 734 | I 76 ST,,I 76,ST, 735 | I 76 ST,,I 76,ST, 736 | I 76 ST,,I 76,ST, 737 | I 76 ST,,I 76,ST, 738 | I 76 ST,,I 76,ST, 739 | I 76 ST,,I 76,ST, 740 | I 76 ST,,I 76,ST, 741 | I 76 ST,,I 76,ST, 742 | I 76 ST,,I 76,ST, 743 | I 76 ST,,I 76,ST, 744 | I 76 ST,,I 76,ST, 745 | I 76 ST,,I 76,ST, 746 | I 76 ST,,I 76,ST, 747 | I 76 ST,,I 76,ST, 748 | I 76 ST,,I 76,ST, 749 | I 76 ST,,I 76,ST, 750 | I 76 ST,,I 76,ST, 751 | I 76 ST,,I 76,ST, 752 | I 76 ST,,I 76,ST, 753 | I 76 ST,,I 76,ST, 754 | I 76 ST,,I 76,ST, 755 | I 76 ST,,I 76,ST, 756 | I 76 ST,,I 76,ST, 757 | I 76 ST,,I 76,ST, 758 | I 76,,I 76,, 759 | I 76,,I 76,, 760 | I 76,,I 76,, 761 | I 76,,I 76,, 762 | I 76,,I 76,, 763 | I 76,,I 76,, 764 | I 76,,I 76,, 765 | I 76,,I 76,, 766 | I 76,,I 76,, 767 | I 76,,I 76,, 768 | I 76,,I 76,, 769 | I 76,,I 76,, 770 | I 76,,I 76,, 771 | I 76,,I 76,, 772 | I 76,,I 76,, 773 | I 76,,I 76,, 774 | I 76,,I 76,, 775 | I 76,,I 76,, 776 | I 76,,I 76,, 777 | I 76,,I 76,, 778 | I 76,,I 76,, 779 | I 76,,I 76,, 780 | I 76,,I 76,, 781 | I 76,,I 76,, 782 | I 76,,I 76,, 783 | I 76,,I 76,, 784 | I 76,,I 76,, 785 | I 76,,I 76,, 786 | I 76,,I 76,, 787 | I 76,,I 76,, 788 | I 76,,I 76,, 789 | I 76,,I 76,, 790 | I 76,,I 76,, 791 | I 76,,I 76,, 792 | I 76,,I 76,, 793 | I 76,,I 76,, 794 | I 76,,I 76,, 795 | I 76,,I 76,, 796 | I 76,,I 76,, 797 | I 76,,I 76,, 798 | I 76,,I 76,, 799 | I 76,,I 76,, 800 | I 76,,I 76,, 801 | I 76,,I 76,, 802 | I 76,,I 76,, 803 | I 76,,I 76,, 804 | I 76,,I 76,, 805 | I 76,,I 76,, 806 | I 76,,I 76,, 807 | I 76,,I 76,, 808 | I 76,,I 76,, 809 | I 76,,I 76,, 810 | I 76,,I 76,, 811 | I 76,,I 76,, 812 | I 76,,I 76,, 813 | I 76,,I 76,, 814 | I 76,,I 76,, 815 | I 76,,I 76,, 816 | I 76,,I 76,, 817 | I 76,,I 76,, 818 | I 76,,I 76,, 819 | I 76,,I 76,, 820 | I 76,,I 76,, 821 | I 76,,I 76,, 822 | I 76,,I 76,, 823 | I 76,,I 76,, 824 | I 76,,I 76,, 825 | I 76,,I 76,, 826 | I 76,,I 76,, 827 | I 76,,I 76,, 828 | I 76,,I 76,, 829 | NB I95 EXP,NB,I95,EXP, 830 | NB I95 EXP,NB,I95,EXP, 831 | NB I95 EXP,NB,I95,EXP, 832 | NB I95 EXP,NB,I95,EXP, 833 | NB I95 EXP,NB,I95,EXP, 834 | NB I95 EXP,NB,I95,EXP, 835 | NB I95 EXP,NB,I95,EXP, 836 | NB I95 EXP,NB,I95,EXP, 837 | NB I95 EXP,NB,I95,EXP, 838 | NB I95 EXP,NB,I95,EXP, 839 | NB I95 EXP,NB,I95,EXP, 840 | NB I95 EXP,NB,I95,EXP, 841 | NB I95 EXP,NB,I95,EXP, 842 | NB I95 EXP,NB,I95,EXP, 843 | NB I95 EXP,NB,I95,EXP, 844 | NB I95 EXP,NB,I95,EXP, 845 | NB I95 EXP,NB,I95,EXP, 846 | NB I95 EXP,NB,I95,EXP, 847 | NB I95 EXP,NB,I95,EXP, 848 | NB I95 EXP,NB,I95,EXP, 849 | NB I95 EXP,NB,I95,EXP, 850 | NB I95 EXP,NB,I95,EXP, 851 | NB I95 EXP,NB,I95,EXP, 852 | NB I95 EXP,NB,I95,EXP, 853 | NB I95 EXP,NB,I95,EXP, 854 | NB I95 EXP,NB,I95,EXP, 855 | NB I95 EXP,NB,I95,EXP, 856 | NB I95 EXP,NB,I95,EXP, 857 | NB I95 EXP,NB,I95,EXP, 858 | NB I95 EXP,NB,I95,EXP, 859 | NB I95 EXP,NB,I95,EXP, 860 | NB I95 EXP,NB,I95,EXP, 861 | NB I95 EXP,NB,I95,EXP, 862 | NB I95 EXP,NB,I95,EXP, 863 | NB I95 EXP,NB,I95,EXP, 864 | NB I95 EXP,NB,I95,EXP, 865 | NB I95 EXP,NB,I95,EXP, 866 | NB I95 EXP,NB,I95,EXP, 867 | NB I95 EXP,NB,I95,EXP, 868 | NB I95 EXP,NB,I95,EXP, 869 | SB I95 EXP,SB,I95,EXP, 870 | SB I95 EXP,SB,I95,EXP, 871 | SB I95 EXP,SB,I95,EXP, 872 | SB I95 EXP,SB,I95,EXP, 873 | SB I95 EXP,SB,I95,EXP, 874 | SB I95 EXP,SB,I95,EXP, 875 | SB I95 EXP,SB,I95,EXP, 876 | SB I95 EXP,SB,I95,EXP, 877 | SB I95 EXP,SB,I95,EXP, 878 | SB I95 EXP,SB,I95,EXP, 879 | SB I95 EXP,SB,I95,EXP, 880 | SB I95 EXP,SB,I95,EXP, 881 | SB I95 EXP,SB,I95,EXP, 882 | SB I95 EXP,SB,I95,EXP, 883 | SB I95 EXP,SB,I95,EXP, 884 | SB I95 EXP,SB,I95,EXP, 885 | SB I95 EXP,SB,I95,EXP, 886 | SB I95 EXP,SB,I95,EXP, 887 | SB I95 EXP,SB,I95,EXP, 888 | SB I95 EXP,SB,I95,EXP, 889 | SB I95 EXP,SB,I95,EXP, 890 | SB I95 EXP,SB,I95,EXP, 891 | SB I95 EXP,SB,I95,EXP, 892 | SB I95 EXP,SB,I95,EXP, 893 | SB I95 EXP,SB,I95,EXP, 894 | SB I95 EXP,SB,I95,EXP, 895 | SB I95 EXP,SB,I95,EXP, 896 | SB I95 EXP,SB,I95,EXP, 897 | SB I95 EXP,SB,I95,EXP, 898 | SB I95 EXP,SB,I95,EXP, 899 | SB I95 EXP,SB,I95,EXP, 900 | SB I95 EXP,SB,I95,EXP, 901 | SB I95 EXP,SB,I95,EXP, 902 | SB I95 EXP,SB,I95,EXP, 903 | SB I95 EXP,SB,I95,EXP, 904 | SB I95 EXP,SB,I95,EXP, 905 | SB I95 EXP,SB,I95,EXP, 906 | I95 RMP,,I95,RMP, 907 | I95 RMP,,I95,RMP, 908 | I95 RMP,,I95,RMP, 909 | I95 RMP,,I95,RMP, 910 | I95 RMP,,I95,RMP, 911 | I95 RMP,,I95,RMP, 912 | I95 RMP,,I95,RMP, 913 | I95 RMP,,I95,RMP, 914 | I95 RMP,,I95,RMP, 915 | I95 RMP,,I95,RMP, 916 | I95 RMP,,I95,RMP, 917 | I95 RMP,,I95,RMP, 918 | I95 RMP,,I95,RMP, 919 | I95 RMP,,I95,RMP, 920 | I95 RMP,,I95,RMP, 921 | I95 RMP,,I95,RMP, 922 | I95 RMP,,I95,RMP, 923 | I95 RMP,,I95,RMP, 924 | I95 RMP,,I95,RMP, 925 | I95,,I95,, 926 | I95,,I95,, 927 | I95,,I95,, 928 | I95,,I95,, 929 | I95,,I95,, 930 | I95,,I95,, 931 | I95,,I95,, 932 | I95,,I95,, 933 | I95,,I95,, 934 | I95,,I95,, 935 | I95,,I95,, 936 | I95,,I95,, 937 | I95,,I95,, 938 | I95,,I95,, 939 | I95,,I95,, 940 | I95,,I95,, 941 | I95,,I95,, 942 | I95,,I95,, 943 | I95,,I95,, 944 | I95,,I95,, 945 | I95,,I95,, 946 | I95,,I95,, 947 | I95,,I95,, 948 | I95,,I95,, 949 | I95,,I95,, 950 | I95,,I95,, 951 | I95,,I95,, 952 | I95,,I95,, 953 | I95,,I95,, 954 | I95,,I95,, 955 | I95,,I95,, 956 | I95,,I95,, 957 | I95,,I95,, 958 | I95,,I95,, 959 | I95,,I95,, 960 | I95,,I95,, 961 | I95,,I95,, 962 | I95,,I95,, 963 | I95,,I95,, 964 | I95,,I95,, 965 | I95,,I95,, 966 | I95,,I95,, 967 | I95,,I95,, 968 | I95,,I95,, 969 | I95,,I95,, 970 | I95,,I95,, 971 | I95,,I95,, 972 | I95,,I95,, 973 | I95,,I95,, 974 | I95,,I95,, 975 | I95,,I95,, 976 | I95,,I95,, 977 | I95,,I95,, 978 | I95,,I95,, 979 | I95,,I95,, 980 | I95,,I95,, 981 | I95,,I95,, 982 | I95,,I95,, 983 | I95,,I95,, 984 | I95,,I95,, 985 | I95,,I95,, 986 | I95,,I95,, 987 | I95,,I95,, 988 | I95,,I95,, 989 | I95,,I95,, 990 | I95,,I95,, 991 | I95,,I95,, 992 | I95,,I95,, 993 | I95,,I95,, 994 | I95,,I95,, 995 | I95,,I95,, 996 | I95,,I95,, 997 | I95,,I95,, 998 | I95,,I95,, 999 | I95,,I95,, 1000 | I95,,I95,, 1001 | I95,,I95,, 1002 | NB I 95 EXP,NB,I 95,EXP, 1003 | NB I 95 EXP,NB,I 95,EXP, 1004 | NB I 95 EXP,NB,I 95,EXP, 1005 | NB I 95 EXP,NB,I 95,EXP, 1006 | NB I 95 EXP,NB,I 95,EXP, 1007 | NB I 95 EXP,NB,I 95,EXP, 1008 | NB I 95 EXP,NB,I 95,EXP, 1009 | NB I 95 EXP,NB,I 95,EXP, 1010 | NB I 95 EXP,NB,I 95,EXP, 1011 | NB I 95 EXP,NB,I 95,EXP, 1012 | NB I 95 EXP,NB,I 95,EXP, 1013 | NB I 95 EXP,NB,I 95,EXP, 1014 | NB I 95 EXP,NB,I 95,EXP, 1015 | NB I 95 EXP,NB,I 95,EXP, 1016 | NB I 95 EXP,NB,I 95,EXP, 1017 | NB I 95 EXP,NB,I 95,EXP, 1018 | NB I 95 EXP,NB,I 95,EXP, 1019 | NB I 95 EXP,NB,I 95,EXP, 1020 | NB I 95 EXP,NB,I 95,EXP, 1021 | NB I 95 EXP,NB,I 95,EXP, 1022 | NB I 95 EXP,NB,I 95,EXP, 1023 | NB I 95 EXP,NB,I 95,EXP, 1024 | NB I 95 EXP,NB,I 95,EXP, 1025 | NB I 95 EXP,NB,I 95,EXP, 1026 | NB I 95 EXP,NB,I 95,EXP, 1027 | NB I 95 EXP,NB,I 95,EXP, 1028 | NB I 95 EXP,NB,I 95,EXP, 1029 | NB I 95 EXP,NB,I 95,EXP, 1030 | NB I 95 EXP,NB,I 95,EXP, 1031 | NB I 95 EXP,NB,I 95,EXP, 1032 | NB I 95 EXP,NB,I 95,EXP, 1033 | NB I 95 EXP,NB,I 95,EXP, 1034 | NB I 95 EXP,NB,I 95,EXP, 1035 | NB I 95 EXP,NB,I 95,EXP, 1036 | NB I 95 EXP,NB,I 95,EXP, 1037 | NB I 95 EXP,NB,I 95,EXP, 1038 | NB I 95 EXP,NB,I 95,EXP, 1039 | NB I 95 EXP,NB,I 95,EXP, 1040 | NB I 95 EXP,NB,I 95,EXP, 1041 | NB I 95 EXP,NB,I 95,EXP, 1042 | SB I 95 EXP,SB,I 95,EXP, 1043 | SB I 95 EXP,SB,I 95,EXP, 1044 | SB I 95 EXP,SB,I 95,EXP, 1045 | SB I 95 EXP,SB,I 95,EXP, 1046 | SB I 95 EXP,SB,I 95,EXP, 1047 | SB I 95 EXP,SB,I 95,EXP, 1048 | SB I 95 EXP,SB,I 95,EXP, 1049 | SB I 95 EXP,SB,I 95,EXP, 1050 | SB I 95 EXP,SB,I 95,EXP, 1051 | SB I 95 EXP,SB,I 95,EXP, 1052 | SB I 95 EXP,SB,I 95,EXP, 1053 | SB I 95 EXP,SB,I 95,EXP, 1054 | SB I 95 EXP,SB,I 95,EXP, 1055 | SB I 95 EXP,SB,I 95,EXP, 1056 | SB I 95 EXP,SB,I 95,EXP, 1057 | SB I 95 EXP,SB,I 95,EXP, 1058 | SB I 95 EXP,SB,I 95,EXP, 1059 | SB I 95 EXP,SB,I 95,EXP, 1060 | SB I 95 EXP,SB,I 95,EXP, 1061 | SB I 95 EXP,SB,I 95,EXP, 1062 | SB I 95 EXP,SB,I 95,EXP, 1063 | SB I 95 EXP,SB,I 95,EXP, 1064 | SB I 95 EXP,SB,I 95,EXP, 1065 | SB I 95 EXP,SB,I 95,EXP, 1066 | SB I 95 EXP,SB,I 95,EXP, 1067 | SB I 95 EXP,SB,I 95,EXP, 1068 | SB I 95 EXP,SB,I 95,EXP, 1069 | SB I 95 EXP,SB,I 95,EXP, 1070 | SB I 95 EXP,SB,I 95,EXP, 1071 | SB I 95 EXP,SB,I 95,EXP, 1072 | SB I 95 EXP,SB,I 95,EXP, 1073 | SB I 95 EXP,SB,I 95,EXP, 1074 | SB I 95 EXP,SB,I 95,EXP, 1075 | SB I 95 EXP,SB,I 95,EXP, 1076 | SB I 95 EXP,SB,I 95,EXP, 1077 | SB I 95 EXP,SB,I 95,EXP, 1078 | SB I 95 EXP,SB,I 95,EXP, 1079 | I 95 RMP,,I 95,RMP, 1080 | I 95 RMP,,I 95,RMP, 1081 | I 95 RMP,,I 95,RMP, 1082 | I 95 RMP,,I 95,RMP, 1083 | I 95 RMP,,I 95,RMP, 1084 | I 95 RMP,,I 95,RMP, 1085 | I 95 RMP,,I 95,RMP, 1086 | I 95 RMP,,I 95,RMP, 1087 | I 95 RMP,,I 95,RMP, 1088 | I 95 RMP,,I 95,RMP, 1089 | I 95 RMP,,I 95,RMP, 1090 | I 95 RMP,,I 95,RMP, 1091 | I 95 RMP,,I 95,RMP, 1092 | I 95 RMP,,I 95,RMP, 1093 | I 95 RMP,,I 95,RMP, 1094 | I 95 RMP,,I 95,RMP, 1095 | I 95 RMP,,I 95,RMP, 1096 | I 95 RMP,,I 95,RMP, 1097 | I 95 RMP,,I 95,RMP, 1098 | I 95,,I 95,, 1099 | I 95,,I 95,, 1100 | I 95,,I 95,, 1101 | I 95,,I 95,, 1102 | I 95,,I 95,, 1103 | I 95,,I 95,, 1104 | I 95,,I 95,, 1105 | I 95,,I 95,, 1106 | I 95,,I 95,, 1107 | I 95,,I 95,, 1108 | I 95,,I 95,, 1109 | I 95,,I 95,, 1110 | I 95,,I 95,, 1111 | I 95,,I 95,, 1112 | I 95,,I 95,, 1113 | I 95,,I 95,, 1114 | I 95,,I 95,, 1115 | I 95,,I 95,, 1116 | I 95,,I 95,, 1117 | I 95,,I 95,, 1118 | I 95,,I 95,, 1119 | I 95,,I 95,, 1120 | I 95,,I 95,, 1121 | I 95,,I 95,, 1122 | I 95,,I 95,, 1123 | I 95,,I 95,, 1124 | I 95,,I 95,, 1125 | I 95,,I 95,, 1126 | I 95,,I 95,, 1127 | I 95,,I 95,, 1128 | I 95,,I 95,, 1129 | I 95,,I 95,, 1130 | I 95,,I 95,, 1131 | I 95,,I 95,, 1132 | I 95,,I 95,, 1133 | I 95,,I 95,, 1134 | I 95,,I 95,, 1135 | I 95,,I 95,, 1136 | I 95,,I 95,, 1137 | I 95,,I 95,, 1138 | I 95,,I 95,, 1139 | I 95,,I 95,, 1140 | I 95,,I 95,, 1141 | I 95,,I 95,, 1142 | I 95,,I 95,, 1143 | I 95,,I 95,, 1144 | I 95,,I 95,, 1145 | I 95,,I 95,, 1146 | I 95,,I 95,, 1147 | I 95,,I 95,, 1148 | I 95,,I 95,, 1149 | I 95,,I 95,, 1150 | I 95,,I 95,, 1151 | I 95,,I 95,, 1152 | I 95,,I 95,, 1153 | I 95,,I 95,, 1154 | I 95,,I 95,, 1155 | I 95,,I 95,, 1156 | I 95,,I 95,, 1157 | I 95,,I 95,, 1158 | I 95,,I 95,, 1159 | I 95,,I 95,, 1160 | I 95,,I 95,, 1161 | I 95,,I 95,, 1162 | I 95,,I 95,, 1163 | I 95,,I 95,, 1164 | I 95,,I 95,, 1165 | I 95,,I 95,, 1166 | I 95,,I 95,, 1167 | I 95,,I 95,, 1168 | I 95,,I 95,, 1169 | I 95,,I 95,, 1170 | I 95,,I 95,, 1171 | I 95,,I 95,, 1172 | I 95,,I 95,, 1173 | I 95,,I 95,, 1174 | I 95,,I 95,, 1175 | KEELY ST,,KEELY,ST, 1176 | KELLY DRIVE TR,,KELLY DRIVE,TR, 1177 | E KESWICK RD,E,KESWICK,RD, 1178 | E KESWICK RD,E,KESWICK,RD, 1179 | E KESWICK RD,E,KESWICK,RD, 1180 | E KESWICK RD,E,KESWICK,RD, 1181 | E KESWICK RD,E,KESWICK,RD, 1182 | E KESWICK RD,E,KESWICK,RD, 1183 | E KESWICK RD,E,KESWICK,RD, 1184 | E KESWICK RD,E,KESWICK,RD, 1185 | E KESWICK RD,E,KESWICK,RD, 1186 | E KESWICK RD,E,KESWICK,RD, 1187 | LAWNDALE AVE,,LAWNDALE,AVE, 1188 | LAWNDALE AVE,,LAWNDALE,AVE, 1189 | LAWNDALE AVE,,LAWNDALE,AVE, 1190 | LAWNDALE AVE,,LAWNDALE,AVE, 1191 | LAWNDALE AVE,,LAWNDALE,AVE, 1192 | LAWNDALE AVE,,LAWNDALE,AVE, 1193 | LAWNDALE AVE,,LAWNDALE,AVE, 1194 | LAWNDALE AVE,,LAWNDALE,AVE, 1195 | LAWNDALE AVE,,LAWNDALE,AVE, 1196 | LAWNDALE AVE,,LAWNDALE,AVE, 1197 | LAWNDALE AVE,,LAWNDALE,AVE, 1198 | LAWNDALE AVE,,LAWNDALE,AVE, 1199 | LIVEZEY LN,,LIVEZEY,LN, 1200 | LIVEZEY LN,,LIVEZEY,LN, 1201 | LIVEZEY LN,,LIVEZEY,LN, 1202 | LIVEZEY LN,,LIVEZEY,LN, 1203 | LIVEZEY LN,,LIVEZEY,LN, 1204 | LIVEZEY LN,,LIVEZEY,LN, 1205 | LIVEZEY LN,,LIVEZEY,LN, 1206 | LIVEZEY LN,,LIVEZEY,LN, 1207 | LIVEZEY LN,,LIVEZEY,LN, 1208 | LIVEZEY LN,,LIVEZEY,LN, 1209 | LIVEZEY LN,,LIVEZEY,LN, 1210 | LIVEZEY ST,,LIVEZEY,ST, 1211 | LIVEZEY ST,,LIVEZEY,ST, 1212 | LIVEZEY ST,,LIVEZEY,ST, 1213 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1214 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1215 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1216 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1217 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1218 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1219 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1220 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1221 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1222 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1223 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1224 | MANAYUNK TOWPATH TR,,MANAYUNK TOWPATH,TR, 1225 | MANNING ST,,MANNING,ST, 1226 | MAPLEWOOD AVE,,MAPLEWOOD,AVE, 1227 | MAYLAND ST,,MAYLAND,ST, 1228 | MOWER ST,,MOWER,ST, 1229 | NORTHEAST AVE,,NORTHEAST,AVE, 1230 | NORTHEAST AVE,,NORTHEAST,AVE, 1231 | NORTHEAST AVE,,NORTHEAST,AVE, 1232 | NORTHEAST AVE,,NORTHEAST,AVE, 1233 | NORTHEAST AVE,,NORTHEAST,AVE, 1234 | NORTHEAST AVE,,NORTHEAST,AVE, 1235 | NORTHEAST AVE,,NORTHEAST,AVE, 1236 | NORTHEAST AVE,,NORTHEAST,AVE, 1237 | NORTHEAST AVE,,NORTHEAST,AVE, 1238 | NORTHEAST AVE,,NORTHEAST,AVE, 1239 | NORTHEAST AVE,,NORTHEAST,AVE, 1240 | OAK LANE AVE,,OAK LANE,AVE, 1241 | OAK LANE AVE,,OAK LANE,AVE, 1242 | OAK LANE AVE,,OAK LANE,AVE, 1243 | OAK LANE AVE,,OAK LANE,AVE, 1244 | OAK LANE AVE,,OAK LANE,AVE, 1245 | OAK LANE AVE,,OAK LANE,AVE, 1246 | OAK LANE AVE,,OAK LANE,AVE, 1247 | OAK LANE AVE,,OAK LANE,AVE, 1248 | OAK LANE AVE,,OAK LANE,AVE, 1249 | OAK LANE AVE,,OAK LANE,AVE, 1250 | OAK LANE AVE,,OAK LANE,AVE, 1251 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1252 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1253 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1254 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1255 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1256 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1257 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1258 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1259 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1260 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1261 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1262 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1263 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1264 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1265 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1266 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1267 | PENNSYLVANIA 291 HWY,,PENNSYLVANIA 291,HWY, 1268 | PRESTON ST,,PRESTON,ST, 1269 | PRESTON ST,,PRESTON,ST, 1270 | RECIRCULATION RD,,RECIRCULATION,RD, 1271 | RECIRCULATION RD,,RECIRCULATION,RD, 1272 | E RIVER RD,E,RIVER,RD, 1273 | E RIVER RD,E,RIVER,RD, 1274 | W RIVER DR,W,RIVER,DR, 1275 | W RIVER DR,W,RIVER,DR, 1276 | W RIVER DR,W,RIVER,DR, 1277 | W RIVER DR,W,RIVER,DR, 1278 | W RIVER DR,W,RIVER,DR, 1279 | W RIVER DR,W,RIVER,DR, 1280 | W RIVER DR,W,RIVER,DR, 1281 | W RIVER DR,W,RIVER,DR, 1282 | W RIVER DR,W,RIVER,DR, 1283 | W RIVER DR,W,RIVER,DR, 1284 | W RIVER DR,W,RIVER,DR, 1285 | ROUTE 1,,ROUTE 1,, 1286 | ROUTE 1,,ROUTE 1,, 1287 | ROUTE 1,,ROUTE 1,, 1288 | ROUTE 1,,ROUTE 1,, 1289 | ROUTE 1,,ROUTE 1,, 1290 | ROUTE 1,,ROUTE 1,, 1291 | ROUTE 1,,ROUTE 1,, 1292 | ROUTE 1,,ROUTE 1,, 1293 | ROUTE 1,,ROUTE 1,, 1294 | ROUTE 1,,ROUTE 1,, 1295 | ROUTE 1,,ROUTE 1,, 1296 | ROUTE 1,,ROUTE 1,, 1297 | ROUTE 1,,ROUTE 1,, 1298 | ROUTE 1,,ROUTE 1,, 1299 | ROUTE 1,,ROUTE 1,, 1300 | ROUTE 1,,ROUTE 1,, 1301 | ROUTE 1,,ROUTE 1,, 1302 | ROUTE 1,,ROUTE 1,, 1303 | ROUTE 1,,ROUTE 1,, 1304 | ROUTE 1,,ROUTE 1,, 1305 | ROUTE 1,,ROUTE 1,, 1306 | ROUTE 1,,ROUTE 1,, 1307 | ROUTE 1,,ROUTE 1,, 1308 | ROUTE 1,,ROUTE 1,, 1309 | ROUTE 1,,ROUTE 1,, 1310 | ROUTE 1,,ROUTE 1,, 1311 | ROUTE 1,,ROUTE 1,, 1312 | ROUTE 1,,ROUTE 1,, 1313 | ROUTE 1,,ROUTE 1,, 1314 | ROUTE 1,,ROUTE 1,, 1315 | ROUTE 1,,ROUTE 1,, 1316 | ROUTE 1,,ROUTE 1,, 1317 | ROUTE 1,,ROUTE 1,, 1318 | ROUTE 1,,ROUTE 1,, 1319 | ROUTE 1,,ROUTE 1,, 1320 | ROUTE 1,,ROUTE 1,, 1321 | ROUTE 1,,ROUTE 1,, 1322 | ROUTE 1,,ROUTE 1,, 1323 | ROUTE 1,,ROUTE 1,, 1324 | ROUTE 1,,ROUTE 1,, 1325 | ROUTE 1,,ROUTE 1,, 1326 | ROUTE 1,,ROUTE 1,, 1327 | ROUTE 1,,ROUTE 1,, 1328 | ROUTE 1,,ROUTE 1,, 1329 | ROUTE 1,,ROUTE 1,, 1330 | ROUTE 1,,ROUTE 1,, 1331 | ROUTE 1,,ROUTE 1,, 1332 | ROUTE 1,,ROUTE 1,, 1333 | ROUTE 1,,ROUTE 1,, 1334 | ROUTE 1,,ROUTE 1,, 1335 | ROUTE 1,,ROUTE 1,, 1336 | ROUTE 1,,ROUTE 1,, 1337 | ROUTE 1,,ROUTE 1,, 1338 | ROUTE 1,,ROUTE 1,, 1339 | ROUTE 1,,ROUTE 1,, 1340 | ROUTE 1,,ROUTE 1,, 1341 | ROUTE 1,,ROUTE 1,, 1342 | ROUTE 1,,ROUTE 1,, 1343 | ROUTE 1,,ROUTE 1,, 1344 | ROUTE 1,,ROUTE 1,, 1345 | ROUTE 1,,ROUTE 1,, 1346 | ROUTE 1,,ROUTE 1,, 1347 | ROUTE 1,,ROUTE 1,, 1348 | ROUTE 1,,ROUTE 1,, 1349 | ROUTE 1,,ROUTE 1,, 1350 | ROUTE 1,,ROUTE 1,, 1351 | ROUTE 1,,ROUTE 1,, 1352 | ROUTE 1,,ROUTE 1,, 1353 | ROUTE 1,,ROUTE 1,, 1354 | ROUTE 1,,ROUTE 1,, 1355 | ROUTE 1,,ROUTE 1,, 1356 | ROUTE 1,,ROUTE 1,, 1357 | ROUTE 1,,ROUTE 1,, 1358 | ROUTE 1,,ROUTE 1,, 1359 | ROUTE 1,,ROUTE 1,, 1360 | ROUTE 1,,ROUTE 1,, 1361 | ROUTE 1,,ROUTE 1,, 1362 | ROUTE 1,,ROUTE 1,, 1363 | ROUTE 1,,ROUTE 1,, 1364 | ROUTE 1,,ROUTE 1,, 1365 | ROUTE 1,,ROUTE 1,, 1366 | ROUTE 1,,ROUTE 1,, 1367 | ROUTE 1,,ROUTE 1,, 1368 | ROUTE 1,,ROUTE 1,, 1369 | ROUTE 1,,ROUTE 1,, 1370 | ROUTE 1,,ROUTE 1,, 1371 | ROUTE 1,,ROUTE 1,, 1372 | ROUTE 1,,ROUTE 1,, 1373 | ROUTE 1,,ROUTE 1,, 1374 | ROUTE 1,,ROUTE 1,, 1375 | ROUTE 1,,ROUTE 1,, 1376 | ROUTE 1,,ROUTE 1,, 1377 | ROUTE 1,,ROUTE 1,, 1378 | ROUTE 1,,ROUTE 1,, 1379 | ROUTE 1,,ROUTE 1,, 1380 | ROUTE 1,,ROUTE 1,, 1381 | ROUTE 1,,ROUTE 1,, 1382 | ROUTE 1,,ROUTE 1,, 1383 | ROUTE 1,,ROUTE 1,, 1384 | ROUTE 1,,ROUTE 1,, 1385 | ROUTE 1,,ROUTE 1,, 1386 | ROUTE 1,,ROUTE 1,, 1387 | ROUTE 1,,ROUTE 1,, 1388 | ROUTE 1,,ROUTE 1,, 1389 | ROUTE 1,,ROUTE 1,, 1390 | ROUTE 1,,ROUTE 1,, 1391 | ROUTE 1,,ROUTE 1,, 1392 | ROUTE 1,,ROUTE 1,, 1393 | ROUTE 1,,ROUTE 1,, 1394 | ROUTE 1,,ROUTE 1,, 1395 | ROUTE 1,,ROUTE 1,, 1396 | ROUTE 1,,ROUTE 1,, 1397 | ROUTE 1,,ROUTE 1,, 1398 | ROUTE 1,,ROUTE 1,, 1399 | ROUTE 1,,ROUTE 1,, 1400 | ROUTE 1,,ROUTE 1,, 1401 | ROUTE 1,,ROUTE 1,, 1402 | ROUTE 1,,ROUTE 1,, 1403 | ROUTE 1,,ROUTE 1,, 1404 | ROUTE 1,,ROUTE 1,, 1405 | ROUTE 1,,ROUTE 1,, 1406 | ROUTE 1,,ROUTE 1,, 1407 | ROUTE 1,,ROUTE 1,, 1408 | ROUTE 1,,ROUTE 1,, 1409 | ROUTE 1,,ROUTE 1,, 1410 | ROUTE 1,,ROUTE 1,, 1411 | ROUTE 1,,ROUTE 1,, 1412 | ROUTE 1,,ROUTE 1,, 1413 | ROUTE 1,,ROUTE 1,, 1414 | ROUTE 1,,ROUTE 1,, 1415 | ROUTE 1,,ROUTE 1,, 1416 | ROUTE 1,,ROUTE 1,, 1417 | ROUTE 1,,ROUTE 1,, 1418 | ROUTE 1,,ROUTE 1,, 1419 | ROUTE 1,,ROUTE 1,, 1420 | ROUTE 1,,ROUTE 1,, 1421 | ROUTE 1,,ROUTE 1,, 1422 | ROUTE 1,,ROUTE 1,, 1423 | ROUTE 1,,ROUTE 1,, 1424 | ROUTE 1,,ROUTE 1,, 1425 | ROUTE 1,,ROUTE 1,, 1426 | ROUTE 1,,ROUTE 1,, 1427 | ROUTE 1,,ROUTE 1,, 1428 | ROUTE 1,,ROUTE 1,, 1429 | ROUTE 1,,ROUTE 1,, 1430 | ROUTE 1,,ROUTE 1,, 1431 | ROUTE 1,,ROUTE 1,, 1432 | ROUTE 1,,ROUTE 1,, 1433 | ROUTE 1,,ROUTE 1,, 1434 | ROUTE 1,,ROUTE 1,, 1435 | ROUTE 1,,ROUTE 1,, 1436 | ROUTE 1,,ROUTE 1,, 1437 | ROUTE 1,,ROUTE 1,, 1438 | ROUTE 1,,ROUTE 1,, 1439 | ROUTE 1,,ROUTE 1,, 1440 | ROUTE 1,,ROUTE 1,, 1441 | ROUTE 1,,ROUTE 1,, 1442 | ROUTE 1,,ROUTE 1,, 1443 | ROUTE 1,,ROUTE 1,, 1444 | ROUTE 1,,ROUTE 1,, 1445 | ROUTE 1,,ROUTE 1,, 1446 | ROUTE 1,,ROUTE 1,, 1447 | ROUTE 1,,ROUTE 1,, 1448 | ROUTE 1,,ROUTE 1,, 1449 | ROUTE 1,,ROUTE 1,, 1450 | ROUTE 1,,ROUTE 1,, 1451 | ROUTE 1,,ROUTE 1,, 1452 | ROUTE 1,,ROUTE 1,, 1453 | ROUTE 1,,ROUTE 1,, 1454 | ROUTE 1,,ROUTE 1,, 1455 | ROUTE 1,,ROUTE 1,, 1456 | ROUTE 1,,ROUTE 1,, 1457 | ROUTE 1,,ROUTE 1,, 1458 | ROUTE 1,,ROUTE 1,, 1459 | ROUTE 1,,ROUTE 1,, 1460 | ROUTE 1,,ROUTE 1,, 1461 | ROUTE 1,,ROUTE 1,, 1462 | ROUTE 1,,ROUTE 1,, 1463 | ROUTE 1,,ROUTE 1,, 1464 | ROUTE 1,,ROUTE 1,, 1465 | ROUTE 1,,ROUTE 1,, 1466 | ROUTE 1,,ROUTE 1,, 1467 | ROUTE 1,,ROUTE 1,, 1468 | ROUTE 1,,ROUTE 1,, 1469 | ROUTE 1,,ROUTE 1,, 1470 | ROUTE 1,,ROUTE 1,, 1471 | ROUTE 1,,ROUTE 1,, 1472 | ROUTE 1,,ROUTE 1,, 1473 | ROUTE 1,,ROUTE 1,, 1474 | ROUTE 1,,ROUTE 1,, 1475 | ROUTE 1,,ROUTE 1,, 1476 | ROUTE 1,,ROUTE 1,, 1477 | ROUTE 1,,ROUTE 1,, 1478 | ROUTE 1,,ROUTE 1,, 1479 | ROUTE 1,,ROUTE 1,, 1480 | ROUTE 1,,ROUTE 1,, 1481 | ROUTE 1,,ROUTE 1,, 1482 | ROUTE 1,,ROUTE 1,, 1483 | ROUTE 1,,ROUTE 1,, 1484 | ROUTE 1,,ROUTE 1,, 1485 | ROUTE 1,,ROUTE 1,, 1486 | ROUTE 1,,ROUTE 1,, 1487 | ROUTE 1,,ROUTE 1,, 1488 | ROUTE 1,,ROUTE 1,, 1489 | ROUTE 1,,ROUTE 1,, 1490 | ROUTE 1,,ROUTE 1,, 1491 | ROUTE 1,,ROUTE 1,, 1492 | ROUTE 1,,ROUTE 1,, 1493 | ROUTE 1,,ROUTE 1,, 1494 | ROUTE 1,,ROUTE 1,, 1495 | ROUTE 1,,ROUTE 1,, 1496 | ROUTE 1,,ROUTE 1,, 1497 | ROUTE 1,,ROUTE 1,, 1498 | ROUTE 1,,ROUTE 1,, 1499 | ROUTE 1,,ROUTE 1,, 1500 | ROUTE 1,,ROUTE 1,, 1501 | ROUTE 1,,ROUTE 1,, 1502 | ROUTE 1,,ROUTE 1,, 1503 | ROUTE 1,,ROUTE 1,, 1504 | ROUTE 1,,ROUTE 1,, 1505 | ROUTE 1,,ROUTE 1,, 1506 | ROUTE 1,,ROUTE 1,, 1507 | ROUTE 1,,ROUTE 1,, 1508 | ROUTE 1,,ROUTE 1,, 1509 | ROUTE 1,,ROUTE 1,, 1510 | ROUTE 1,,ROUTE 1,, 1511 | ROUTE 1,,ROUTE 1,, 1512 | ROUTE 1,,ROUTE 1,, 1513 | ROUTE 1,,ROUTE 1,, 1514 | ROUTE 1,,ROUTE 1,, 1515 | ROUTE 1,,ROUTE 1,, 1516 | ROUTE 1,,ROUTE 1,, 1517 | ROUTE 1,,ROUTE 1,, 1518 | ROUTE 1,,ROUTE 1,, 1519 | ROUTE 1,,ROUTE 1,, 1520 | ROUTE 1,,ROUTE 1,, 1521 | ROUTE 1,,ROUTE 1,, 1522 | ROUTE 1,,ROUTE 1,, 1523 | ROUTE 1,,ROUTE 1,, 1524 | ROUTE 1,,ROUTE 1,, 1525 | ROUTE 1,,ROUTE 1,, 1526 | ROUTE 1,,ROUTE 1,, 1527 | ROUTE 1,,ROUTE 1,, 1528 | ROUTE 1,,ROUTE 1,, 1529 | ROUTE 1,,ROUTE 1,, 1530 | ROUTE 1,,ROUTE 1,, 1531 | ROUTE 1,,ROUTE 1,, 1532 | ROUTE 1,,ROUTE 1,, 1533 | ROUTE 1,,ROUTE 1,, 1534 | ROUTE 1,,ROUTE 1,, 1535 | ROUTE 1,,ROUTE 1,, 1536 | ROUTE 1,,ROUTE 1,, 1537 | ROUTE 1,,ROUTE 1,, 1538 | ROUTE 1,,ROUTE 1,, 1539 | ROUTE 1,,ROUTE 1,, 1540 | ROUTE 1,,ROUTE 1,, 1541 | ROUTE 1,,ROUTE 1,, 1542 | ROUTE 1,,ROUTE 1,, 1543 | ROUTE 1,,ROUTE 1,, 1544 | ROUTE 1,,ROUTE 1,, 1545 | ROUTE 1,,ROUTE 1,, 1546 | ROUTE 1,,ROUTE 1,, 1547 | ROUTE 1,,ROUTE 1,, 1548 | ROUTE 1,,ROUTE 1,, 1549 | ROUTE 1,,ROUTE 1,, 1550 | ROUTE 1,,ROUTE 1,, 1551 | ROUTE 1,,ROUTE 1,, 1552 | ROUTE 1,,ROUTE 1,, 1553 | ROUTE 1,,ROUTE 1,, 1554 | ROUTE 1,,ROUTE 1,, 1555 | ROUTE 1,,ROUTE 1,, 1556 | ROUTE 1,,ROUTE 1,, 1557 | ROUTE 1,,ROUTE 1,, 1558 | ROUTE 1,,ROUTE 1,, 1559 | ROUTE 1,,ROUTE 1,, 1560 | ROUTE 1,,ROUTE 1,, 1561 | ROUTE 1,,ROUTE 1,, 1562 | ROUTE 1,,ROUTE 1,, 1563 | ROUTE 1,,ROUTE 1,, 1564 | ROUTE 1,,ROUTE 1,, 1565 | ROUTE 1,,ROUTE 1,, 1566 | ROUTE 1,,ROUTE 1,, 1567 | ROUTE 1,,ROUTE 1,, 1568 | ROUTE 1,,ROUTE 1,, 1569 | ROUTE 1,,ROUTE 1,, 1570 | ROUTE 1,,ROUTE 1,, 1571 | ROUTE 1,,ROUTE 1,, 1572 | ROUTE 1,,ROUTE 1,, 1573 | ROUTE 1,,ROUTE 1,, 1574 | ROUTE 1,,ROUTE 1,, 1575 | ROUTE 1,,ROUTE 1,, 1576 | ROUTE 1,,ROUTE 1,, 1577 | ROUTE 1,,ROUTE 1,, 1578 | ROUTE 1,,ROUTE 1,, 1579 | ROUTE 1,,ROUTE 1,, 1580 | ROUTE 1,,ROUTE 1,, 1581 | ROUTE 1,,ROUTE 1,, 1582 | ROUTE 1,,ROUTE 1,, 1583 | ROUTE 1,,ROUTE 1,, 1584 | ROUTE 1,,ROUTE 1,, 1585 | ROUTE 1,,ROUTE 1,, 1586 | ROUTE 1,,ROUTE 1,, 1587 | ROUTE 1,,ROUTE 1,, 1588 | ROUTE 1,,ROUTE 1,, 1589 | ROUTE 1,,ROUTE 1,, 1590 | ROUTE 1,,ROUTE 1,, 1591 | ROUTE 1,,ROUTE 1,, 1592 | ROUTE 1,,ROUTE 1,, 1593 | ROUTE 1,,ROUTE 1,, 1594 | ROUTE 1,,ROUTE 1,, 1595 | ROUTE 1,,ROUTE 1,, 1596 | ROUTE 1,,ROUTE 1,, 1597 | ROUTE 1,,ROUTE 1,, 1598 | ROUTE 1,,ROUTE 1,, 1599 | ROUTE 1,,ROUTE 1,, 1600 | ROUTE 1,,ROUTE 1,, 1601 | ROUTE 1,,ROUTE 1,, 1602 | ROUTE 1,,ROUTE 1,, 1603 | ROUTE 1,,ROUTE 1,, 1604 | ROUTE 1,,ROUTE 1,, 1605 | ROUTE 1,,ROUTE 1,, 1606 | ROUTE 1,,ROUTE 1,, 1607 | ROUTE 1,,ROUTE 1,, 1608 | ROUTE 1,,ROUTE 1,, 1609 | ROUTE 1,,ROUTE 1,, 1610 | ROUTE 1,,ROUTE 1,, 1611 | ROUTE 1,,ROUTE 1,, 1612 | ROUTE 1,,ROUTE 1,, 1613 | ROUTE 1,,ROUTE 1,, 1614 | ROUTE 1,,ROUTE 1,, 1615 | ROUTE 1,,ROUTE 1,, 1616 | ROUTE 1,,ROUTE 1,, 1617 | ROUTE 1,,ROUTE 1,, 1618 | ROUTE 1,,ROUTE 1,, 1619 | ROUTE 1,,ROUTE 1,, 1620 | ROUTE 1,,ROUTE 1,, 1621 | ROUTE 1,,ROUTE 1,, 1622 | ROUTE 1,,ROUTE 1,, 1623 | ROUTE 1,,ROUTE 1,, 1624 | ROUTE 1,,ROUTE 1,, 1625 | ROUTE 1,,ROUTE 1,, 1626 | ROUTE 1,,ROUTE 1,, 1627 | ROUTE 1,,ROUTE 1,, 1628 | ROUTE 1,,ROUTE 1,, 1629 | ROUTE 1,,ROUTE 1,, 1630 | ROUTE 1,,ROUTE 1,, 1631 | ROUTE 1,,ROUTE 1,, 1632 | ROUTE 1,,ROUTE 1,, 1633 | ROUTE 1,,ROUTE 1,, 1634 | ROUTE 1,,ROUTE 1,, 1635 | ROUTE 1,,ROUTE 1,, 1636 | ROUTE 1,,ROUTE 1,, 1637 | ROUTE 1,,ROUTE 1,, 1638 | ROUTE 1,,ROUTE 1,, 1639 | ROUTE 1,,ROUTE 1,, 1640 | ROUTE 1,,ROUTE 1,, 1641 | ROUTE 1,,ROUTE 1,, 1642 | ROUTE 1,,ROUTE 1,, 1643 | ROUTE 1,,ROUTE 1,, 1644 | ROUTE 1,,ROUTE 1,, 1645 | ROUTE 1,,ROUTE 1,, 1646 | ROUTE 1,,ROUTE 1,, 1647 | ROUTE 1,,ROUTE 1,, 1648 | ROUTE 1,,ROUTE 1,, 1649 | ROUTE 1,,ROUTE 1,, 1650 | ROUTE 1,,ROUTE 1,, 1651 | ROUTE 1,,ROUTE 1,, 1652 | ROUTE 1,,ROUTE 1,, 1653 | ROUTE 1,,ROUTE 1,, 1654 | ROUTE 1,,ROUTE 1,, 1655 | ROUTE 1,,ROUTE 1,, 1656 | ROUTE 1,,ROUTE 1,, 1657 | ROUTE 1,,ROUTE 1,, 1658 | ROUTE 1,,ROUTE 1,, 1659 | ROUTE 1,,ROUTE 1,, 1660 | ROUTE 1,,ROUTE 1,, 1661 | ROUTE 1,,ROUTE 1,, 1662 | ROUTE 1,,ROUTE 1,, 1663 | ROUTE 1,,ROUTE 1,, 1664 | ROUTE 1,,ROUTE 1,, 1665 | ROUTE 1,,ROUTE 1,, 1666 | ROUTE 1,,ROUTE 1,, 1667 | ROUTE 1,,ROUTE 1,, 1668 | ROUTE 1,,ROUTE 1,, 1669 | ROUTE 1,,ROUTE 1,, 1670 | ROUTE 1,,ROUTE 1,, 1671 | ROUTE 1,,ROUTE 1,, 1672 | ROUTE 1,,ROUTE 1,, 1673 | ROUTE 1,,ROUTE 1,, 1674 | ROUTE 1,,ROUTE 1,, 1675 | ROUTE 1,,ROUTE 1,, 1676 | ROUTE 1,,ROUTE 1,, 1677 | ROUTE 1,,ROUTE 1,, 1678 | ROUTE 1,,ROUTE 1,, 1679 | ROUTE 1,,ROUTE 1,, 1680 | ROUTE 1,,ROUTE 1,, 1681 | ROUTE 1,,ROUTE 1,, 1682 | ROUTE 1,,ROUTE 1,, 1683 | ROUTE 1,,ROUTE 1,, 1684 | ROUTE 1,,ROUTE 1,, 1685 | ROUTE 1,,ROUTE 1,, 1686 | ROUTE 1,,ROUTE 1,, 1687 | ROUTE 1,,ROUTE 1,, 1688 | ROUTE 1,,ROUTE 1,, 1689 | ROUTE 1,,ROUTE 1,, 1690 | ROUTE 1,,ROUTE 1,, 1691 | ROUTE 1,,ROUTE 1,, 1692 | ROUTE 1,,ROUTE 1,, 1693 | ROUTE 1,,ROUTE 1,, 1694 | ROUTE 1,,ROUTE 1,, 1695 | ROUTE 1,,ROUTE 1,, 1696 | ROUTE 1,,ROUTE 1,, 1697 | ROUTE 1,,ROUTE 1,, 1698 | ROUTE 1,,ROUTE 1,, 1699 | ROUTE 1,,ROUTE 1,, 1700 | ROUTE 1,,ROUTE 1,, 1701 | ROUTE 1,,ROUTE 1,, 1702 | ROUTE 1,,ROUTE 1,, 1703 | ROUTE 1,,ROUTE 1,, 1704 | ROUTE 1,,ROUTE 1,, 1705 | ROUTE 1,,ROUTE 1,, 1706 | ROUTE 1,,ROUTE 1,, 1707 | ROUTE 1,,ROUTE 1,, 1708 | ROUTE 1,,ROUTE 1,, 1709 | ROUTE 1,,ROUTE 1,, 1710 | ROUTE 1,,ROUTE 1,, 1711 | ROUTE 1,,ROUTE 1,, 1712 | ROUTE 1,,ROUTE 1,, 1713 | ROUTE 1,,ROUTE 1,, 1714 | ROUTE 1,,ROUTE 1,, 1715 | ROUTE 1,,ROUTE 1,, 1716 | ROUTE 1,,ROUTE 1,, 1717 | ROUTE 1,,ROUTE 1,, 1718 | ROUTE 1,,ROUTE 1,, 1719 | ROUTE 1,,ROUTE 1,, 1720 | ROUTE 1,,ROUTE 1,, 1721 | ROUTE 1,,ROUTE 1,, 1722 | ROUTE 1,,ROUTE 1,, 1723 | ROUTE 1,,ROUTE 1,, 1724 | ROUTE 1,,ROUTE 1,, 1725 | ROUTE 1,,ROUTE 1,, 1726 | ROUTE 1,,ROUTE 1,, 1727 | ROUTE 1,,ROUTE 1,, 1728 | ROUTE 1,,ROUTE 1,, 1729 | ROUTE 1,,ROUTE 1,, 1730 | ROUTE 1,,ROUTE 1,, 1731 | ROUTE 1,,ROUTE 1,, 1732 | ROUTE 1,,ROUTE 1,, 1733 | ROUTE 1,,ROUTE 1,, 1734 | ROUTE 1,,ROUTE 1,, 1735 | ROUTE 1,,ROUTE 1,, 1736 | ROUTE 1,,ROUTE 1,, 1737 | ROUTE 1,,ROUTE 1,, 1738 | ROUTE 1,,ROUTE 1,, 1739 | ROUTE 1,,ROUTE 1,, 1740 | ROUTE 1,,ROUTE 1,, 1741 | ROUTE 1,,ROUTE 1,, 1742 | ROUTE 1,,ROUTE 1,, 1743 | ROUTE 1,,ROUTE 1,, 1744 | ROUTE 1,,ROUTE 1,, 1745 | ROUTE 1,,ROUTE 1,, 1746 | ROUTE 1,,ROUTE 1,, 1747 | ROUTE 1,,ROUTE 1,, 1748 | ROUTE 1,,ROUTE 1,, 1749 | ROUTE 1,,ROUTE 1,, 1750 | ROUTE 1,,ROUTE 1,, 1751 | ROUTE 1,,ROUTE 1,, 1752 | ROUTE 1,,ROUTE 1,, 1753 | ROUTE 1,,ROUTE 1,, 1754 | ROUTE 1,,ROUTE 1,, 1755 | ROUTE 1,,ROUTE 1,, 1756 | ROUTE 1,,ROUTE 1,, 1757 | ROUTE 1,,ROUTE 1,, 1758 | ROUTE 1,,ROUTE 1,, 1759 | ROUTE 1,,ROUTE 1,, 1760 | ROUTE 1,,ROUTE 1,, 1761 | ROUTE 1,,ROUTE 1,, 1762 | ROUTE 1,,ROUTE 1,, 1763 | ROUTE 1,,ROUTE 1,, 1764 | ROUTE 1,,ROUTE 1,, 1765 | ROUTE 1,,ROUTE 1,, 1766 | ROUTE 1,,ROUTE 1,, 1767 | ROUTE 1,,ROUTE 1,, 1768 | ROUTE 1,,ROUTE 1,, 1769 | ROUTE 1,,ROUTE 1,, 1770 | ROUTE 1,,ROUTE 1,, 1771 | ROUTE 1,,ROUTE 1,, 1772 | ROUTE 1,,ROUTE 1,, 1773 | ROUTE 1,,ROUTE 1,, 1774 | ROUTE 1,,ROUTE 1,, 1775 | ROUTE 1,,ROUTE 1,, 1776 | ROUTE 1,,ROUTE 1,, 1777 | ROUTE 1,,ROUTE 1,, 1778 | ROUTE 1,,ROUTE 1,, 1779 | ROUTE 1,,ROUTE 1,, 1780 | ROUTE 291 HWY,,ROUTE 291,HWY, 1781 | ROUTE 291 HWY,,ROUTE 291,HWY, 1782 | ROUTE 291 HWY,,ROUTE 291,HWY, 1783 | ROUTE 291 HWY,,ROUTE 291,HWY, 1784 | ROUTE 291 HWY,,ROUTE 291,HWY, 1785 | ROUTE 291 HWY,,ROUTE 291,HWY, 1786 | ROUTE 291 HWY,,ROUTE 291,HWY, 1787 | ROUTE 291 HWY,,ROUTE 291,HWY, 1788 | ROUTE 291 HWY,,ROUTE 291,HWY, 1789 | ROUTE 291 HWY,,ROUTE 291,HWY, 1790 | ROUTE 291 HWY,,ROUTE 291,HWY, 1791 | ROUTE 291 HWY,,ROUTE 291,HWY, 1792 | ROUTE 291 HWY,,ROUTE 291,HWY, 1793 | ROUTE 291 HWY,,ROUTE 291,HWY, 1794 | ROUTE 291 HWY,,ROUTE 291,HWY, 1795 | ROUTE 291 HWY,,ROUTE 291,HWY, 1796 | ROUTE 291 HWY,,ROUTE 291,HWY, 1797 | ROUTE 291,,ROUTE 291,, 1798 | ROUTE 291,,ROUTE 291,, 1799 | ROUTE 291,,ROUTE 291,, 1800 | ROUTE 291,,ROUTE 291,, 1801 | ROUTE 291,,ROUTE 291,, 1802 | ROUTE 291,,ROUTE 291,, 1803 | ROUTE 291,,ROUTE 291,, 1804 | ROUTE 291,,ROUTE 291,, 1805 | ROUTE 291,,ROUTE 291,, 1806 | ROUTE 291,,ROUTE 291,, 1807 | ROUTE 291,,ROUTE 291,, 1808 | ROUTE 291,,ROUTE 291,, 1809 | ROUTE 291,,ROUTE 291,, 1810 | ROUTE 291,,ROUTE 291,, 1811 | ROUTE 291,,ROUTE 291,, 1812 | ROUTE 291,,ROUTE 291,, 1813 | ROUTE 291,,ROUTE 291,, 1814 | ROUTE 291,,ROUTE 291,, 1815 | ROUTE 291,,ROUTE 291,, 1816 | ROUTE 291,,ROUTE 291,, 1817 | ROUTE 291,,ROUTE 291,, 1818 | RUBICAM ST,,RUBICAM,ST, 1819 | RUBICAM ST,,RUBICAM,ST, 1820 | SCHUYLKILL BANKS TR,,SCHUYLKILL BANKS,TR, 1821 | SCHUYLKILL BANKS TR,,SCHUYLKILL BANKS,TR, 1822 | SCHUYLKILL BANKS TR,,SCHUYLKILL BANKS,TR, 1823 | SEROTA DR,,SEROTA,DR, 1824 | SHELLY RD,,SHELLY,RD, 1825 | SORRENTO RD,,SORRENTO,RD, 1826 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1827 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1828 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1829 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1830 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1831 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1832 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1833 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1834 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1835 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1836 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1837 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1838 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1839 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1840 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1841 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1842 | STATE HIGHWAY 291 HWY,,STATE HIGHWAY 291,HWY, 1843 | E STILES ST,E,STILES,ST, 1844 | E STILES ST,E,STILES,ST, 1845 | E STILES ST,E,STILES,ST, 1846 | E TABOR RD,E,TABOR,RD, 1847 | THE ROOSEVELT BLVD,,THE ROOSEVELT,BLVD, 1848 | UNIVERSITY AVE,,UNIVERSITY,AVE, 1849 | UNIVERSITY AVENUE EXTENSION AVE,,UNIVERSITY AVENUE EXTENSION,AVE, 1850 | WALNUT LN,,WALNUT,LN, 1851 | WALNUT LN,,WALNUT,LN, 1852 | WALNUT LN,,WALNUT,LN, 1853 | WEST RIVER DR,,WEST RIVER,DR, 1854 | WEST RIVER DR,,WEST RIVER,DR, 1855 | WEST RIVER DR,,WEST RIVER,DR, 1856 | WEST RIVER DR,,WEST RIVER,DR, 1857 | WEST RIVER DR,,WEST RIVER,DR, 1858 | WEST RIVER DR,,WEST RIVER,DR, 1859 | WEST RIVER DR,,WEST RIVER,DR, 1860 | WEST RIVER DR,,WEST RIVER,DR, 1861 | WEST RIVER DR,,WEST RIVER,DR, 1862 | WEST RIVER DR,,WEST RIVER,DR, 1863 | WEST RIVER DR,,WEST RIVER,DR, 1864 | WEST RIVER DR,,WEST RIVER,DR, 1865 | ZERO ST,,ZERO,ST, 1866 | ZERO ST,,ZERO,ST, 1867 | ZERO ST,,ZERO,ST, 1868 | ZERO ST,,ZERO,ST, 1869 | ZERO ST,,ZERO,ST, 1870 | ZERO ST,,ZERO,ST, 1871 | ZERO ST,,ZERO,ST, 1872 | ZERO ST,,ZERO,ST, 1873 | ZERO ST,,ZERO,ST, 1874 | --------------------------------------------------------------------------------