Skip to content
Browse files

Fleshed out road aliasing system and added proper test cases.

  • Loading branch information...
1 parent 1271cb2 commit 9a5bfb38a37dade90c27085a1450f1deaeba6672 @onyxfish committed
View
73 data/test_fixtures/locate.RoadAlias.json
@@ -0,0 +1,73 @@
+[
+ {
+ "pk": "LOCKWOOD",
+ "model": "locate.roadalias",
+ "fields": {
+ "roads": [
+ "SOUTH LOCKWOOD AVENUE"
+ ],
+ "alias_type": "CA"
+ }
+ },
+ {
+ "pk": "QUINCY",
+ "model": "locate.roadalias",
+ "fields": {
+ "roads": [
+ "WEST QUINCY STREET"
+ ],
+ "alias_type": "CA"
+ }
+ },
+ {
+ "pk": "QIUNCY",
+ "model": "locate.roadalias",
+ "fields": {
+ "roads": [
+ "WEST QUINCY STREET"
+ ],
+ "alias_type": "MS"
+ }
+ },
+ {
+ "pk": "CENTRAL",
+ "model": "locate.roadalias",
+ "fields": {
+ "roads": [
+ "NORTH CENTRAL AVENUE",
+ "SOUTH CENTRAL AVENUE"
+ ],
+ "alias_type": "CA"
+ }
+ },
+ {
+ "pk": "CICERO",
+ "model": "locate.roadalias",
+ "fields": {
+ "roads": [
+ "SOUTH CICERO AVENUE"
+ ],
+ "alias_type": "CA"
+ }
+ },
+ {
+ "pk": "MADISON",
+ "model": "locate.roadalias",
+ "fields": {
+ "roads": [
+ "WEST MADISON STREET"
+ ],
+ "alias_type": "CA"
+ }
+ },
+ {
+ "pk": "HARRISON",
+ "model": "locate.roadalias",
+ "fields": {
+ "roads": [
+ "WEST HARRISON STREET"
+ ],
+ "alias_type": "CA"
+ }
+ }
+]
View
12 safecity/apps/locate/location_parser.py
@@ -133,12 +133,18 @@ def _tokenize_words(self, words):
if word_tokens[i]:
continue
- roads = Road.objects.filter(name__exact=words[i])
-
- if roads:
+ try:
+ # Attempt to grab an alias with this exact spelling
+ alias = RoadAlias.objects.get(name__exact=words[i])
+
# At least one road has this name
word_tokens[i] = TOKEN_ROAD
+ # Rewrite the token with the canonical name
+ words[i] = alias.fetch_canonical_name()
+ except RoadAlias.DoesNotExist:
+ pass
+
return zip(words, word_tokens)
def _substitute_road_args(self, word_tokens):
View
17 safecity/apps/locate/management/commands/load_centerline.py
@@ -28,10 +28,11 @@ class Command(NoArgsCommand):
def handle_noargs(self, **options):
if options['clear']:
- log.info('Clearing all centerline data.')
+ log.info('Clearing all centerline data and aliases.')
Intersection.objects.all().delete()
Block.objects.all().delete()
Road.objects.all().delete()
+ RoadAlias.objects.all().delete()
log.info('Reading shapefile.')
@@ -104,6 +105,20 @@ def get_or_create_road(self, road_prefix_direction, road_name, road_type, road_s
road_type=road_type,
suffix_direction=road_suffix_direction
)
+
+ try:
+ alias = RoadAlias.objects.get(
+ name=road_name,
+ alias_type='CA'
+ )
+ except RoadAlias.DoesNotExist:
+ # Create "root" alias--exactly the original name
+ alias = RoadAlias.objects.create(
+ name=road_name,
+ alias_type='CA'
+ )
+
+ alias.roads.add(road)
return road
View
22 safecity/apps/locate/models.py
@@ -47,6 +47,7 @@
}
ALIAS_TYPES = {
+ 'CA': 'Canonical',
'MS': 'Misspelling',
'HN': 'Honorary Name',
}
@@ -69,7 +70,7 @@ class Road(models.Model):
name = models.CharField(
max_length=64,
db_index=True,
- help_text='The road name.')
+ help_text='The canonical road name.')
road_type = models.CharField(
max_length=4,
@@ -120,18 +121,35 @@ def make_full_name(cls, prefix_direction, name, road_type, suffix_direction):
class RoadAlias(models.Model):
"""
An alternate name for a Road.
+
+ Note that in reality this is joined ManyToMany with Road, because there
+ are version of each road for each direction, etc. However, the name
+ attribute on each of those roads should always be the same. See
+ fetch_canonical_name() for how this is used.
"""
name = models.CharField(
primary_key=True,
max_length=64,
help_text='Alternate name or spelling for a Road.')
- road = models.ForeignKey('Road')
+ roads = models.ManyToManyField('Road')
alias_type = models.CharField(
max_length=2,
choices=ALIAS_TYPES.items(),
help_text='The type of alias this is, e.g. Misspelling or Honorary Name')
+
+ def fetch_canonical_name(self):
+ """
+ Returns the canonical name for the road(s) this is an alias of.
+ """
+ if self.alias_type == 'CA':
+ # Avoid querying if this alias also holds the canonical name
+ return self.name
+ else:
+ # Query any road in the set and return its name
+ a_road = self.roads.all()[0]
+ return a_road.name
class Intersection(models.Model):
"""
View
28 safecity/apps/locate/tests.py
@@ -10,6 +10,7 @@ class TestLocationParser(TestCase):
"""
fixtures = [
'data/test_fixtures/locate.Road.json',
+ 'data/test_fixtures/locate.RoadAlias.json',
'data/test_fixtures/locate.Intersection.json',
'data/test_fixtures/locate.Block.json'
]
@@ -19,7 +20,12 @@ def setUp(self):
self.QUINCY = Road.objects.get(full_name='WEST QUINCY STREET')
self.LOCKWOOD = Road.objects.get(full_name='SOUTH LOCKWOOD AVENUE')
+ self.NORTH_CENTRAL = Road.objects.get(full_name='NORTH CENTRAL AVENUE')
+ self.SOUTH_CENTRAL = Road.objects.get(full_name='SOUTH CENTRAL AVENUE')
+ self.MADISON = Road.objects.get(full_name='WEST MADISON STREET')
self.QUINCY_AND_LOCKWOOD = Intersection.find_intersection(self.QUINCY, self.LOCKWOOD)
+ self.NORTH_CENTRAL_AND_MADISON = Intersection.find_intersection(self.NORTH_CENTRAL, self.MADISON)
+ self.SOUTH_CENTRAL_AND_MADISON = Intersection.find_intersection(self.SOUTH_CENTRAL, self.MADISON)
self.FIFTY_THREE_HUNDRED_QUINCY = Block.objects.get(number=5300, road=self.QUINCY)
# Missing data
@@ -92,6 +98,17 @@ def testIntersectionBadEverything(self):
message = 'N Quincy Ave E & N Lockwood Pkwy W'
self.assertEqual(self.parser.extract_location(message), self.QUINCY_AND_LOCKWOOD)
+ def testIntersectionsEquivalent(self):
+ self.assertEqual(self.NORTH_CENTRAL_AND_MADISON, self.SOUTH_CENTRAL_AND_MADISON)
+
+ def testIntersectionsNorthSouthPartOne(self):
+ message = 'Madison & Central'
+ self.assertEqual(self.parser.extract_location(message), self.NORTH_CENTRAL_AND_MADISON)
+
+ def testIntersectionsNorthSouthPartTwo(self):
+ message = 'Madison & Central'
+ self.assertEqual(self.parser.extract_location(message), self.SOUTH_CENTRAL_AND_MADISON)
+
# Blocks
def testBlock(self):
message = '5300 Quincy'
@@ -129,11 +146,16 @@ def testBlockOf(self):
message = '5300 block of Quincy St'
self.assertEqual(self.parser.extract_location(message), self.FIFTY_THREE_HUNDRED_QUINCY)
- # Edge cases
+ # Unhandled edge cases
def testBetween(self):
message = 'Quincy between Lotus and Lockwood'
- self.assertEqual(self.parser.extract_location(message), self.FIFTY_THREE_HUNDRED_QUINCY)
+ self.assertRaises(NoLocationException, self.parser.extract_location, message)
def testThreeStreets(self):
- message= 'Quincy, Lotus, and Lockwood'
+ message = 'Quincy, Lotus, and Lockwood'
+ self.assertRaises(NoLocationException, self.parser.extract_location, message)
+
+ # Aliases
+ def testAliasMisspelling(self):
+ message = '5300 W Qiuncy'
self.assertEqual(self.parser.extract_location(message), self.FIFTY_THREE_HUNDRED_QUINCY)

0 comments on commit 9a5bfb3

Please sign in to comment.
Something went wrong with that request. Please try again.