X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=src%2FSummaryFile1.py;h=75868673aacd02b72482e506aaa665774e68466c;hb=HEAD;hp=3c82186474108e72908d3d3ded341d48470c1409;hpb=67ebbeeaad11d0c1c934c07692e932708aa3aecf;p=dead%2Fcensus-tools.git diff --git a/src/SummaryFile1.py b/src/SummaryFile1.py index 3c82186..7586867 100644 --- a/src/SummaryFile1.py +++ b/src/SummaryFile1.py @@ -1,51 +1,95 @@ -import os, GPS, inspect +import os -class RecordError(StandardError): - pass +from Errors import RecordError +import GPS +import StringUtils -class InvalidAreaError(StandardError): - pass class GeoRecord: """ This class wraps one record in an SF1 geo file. """ - MinimumLineLength = 400 + MINIMUM_LINE_LENGTH = 400 class Block: """ - Represents a block (which is a special case of a GeoRecord. - All we care about here is the block number, population, - area, and coordinates. + Represents a block (which is a special case of a GeoRecord). + There are some convenience methods tacked on to make computation + and querying easier. """ def __init__(self, geo_record): - """We initialize from a GeoRecord object""" + """ + We initialize from a GeoRecord object. It is important that + we raise some kind of error if there is no 'block' field, since + that means we weren't passed a block. + """ + if not (StringUtils.is_integer(geo_record.block)): + raise RecordError('GeoRecord object does not represent a block.') + + # These need to be stored as strings so they don't + # affect the block_identifier() generation. + self.state = geo_record.state + self.county = geo_record.county + self.tract = geo_record.tract + self.block = geo_record.block + # All of these int/float conversions will throw a ValueError # if the input string cannot be converted o the specified # type. - self.block_number = int(geo_record.block) - self.tract_number = int(geo_record.tract) - self.population = int(geo_record.pop100) - self.area_land = float(geo_record.arealand) - self.area_water = float(geo_record.areawatr) - + self.pop100 = int(geo_record.pop100) + self.arealand = float(geo_record.arealand) + self.areawatr = float(geo_record.areawatr) + + # Both latitude and longitude are given to six digits of + # precision (i.e. after the decimal point). But, there are no + # decimal points in the intptlon/intptlat fields, so we need + # to add them. + # + # By default, the coordinates will be parsed as integers. For + # example, +12345678 will be parsed as 12345678.0. So, we need + # to "move" that decimal point 6 places to the left. We know + # how to do that. + # self.coordinates = GPS.Coordinates() - self.coordinates.latitude = float(geo_record.intptlat) - self.coordinates.longitude = float(geo_record.intptlon) + self.coordinates.latitude = (float(geo_record.intptlat) / (10**6)) + self.coordinates.longitude = (float(geo_record.intptlon) / (10**6)) + + + def blkidfp00(self): + # From the Tiger/Line shapefile documentation: + # + # Current block identifier; a concatenation of Census 2000 + # state FIPS code, Census 2000 county FIPS code, Census + # BLKIDFP 16 String 2000 census tract code, Census 2000 + # tabulation block number, and current block suffix 1. + # + return (self.state + + self.county + + self.tract + + self.block) - if (self.total_area() == 0): - raise InvalidAreaError('A block may not have zero area.') - def total_area(self): - return (self.area_land + self.area_water) + return (self.arealand + self.areawatr) def population_density(self): - return (self.population / self.total_area()) + # There are some unusual cases where a block will have a + # total area of zero. It also seems that these unusual blocks + # do in fact posess geometries, provided in the Tiger database. + # Therefore, we allow them to be parsed. + # + # The choice to assign these blocks an average density of 0 + # was arbitrary. + # + if (self.total_area() == 0): + return 0 + else: + return (self.pop100 / self.total_area()) + class GeoRecordParser: @@ -80,12 +124,12 @@ class GeoRecordParser: try: block = Block(record) blocks.append(block) + except RecordError: + # Ain't a block. + continue except ValueError: # A value couldn't be converted to the appropriate type. continue - except InvalidAreaError: - # Something is funny with the geometry. - continue return blocks @@ -98,8 +142,8 @@ class GeoRecordParser: allow the GeoRecord class to parse the data meaningfully and throw an error if something doesn't look right. """ - if (len(line) < GeoRecord.MinimumLineLength): - raise RecordError("The input line is too short. The SF1 specification requires a line length of %d characters; this line contains only %d characters" % (GeoRecord.MinimumLineLength, len(line))) + if (len(line) < GeoRecord.MINIMUM_LINE_LENGTH): + raise RecordError("The input line is too short. The SF1 specification requires a line length of %d characters; this line contains only %d characters" % (GeoRecord.MINIMUM_LINE_LENGTH, len(line))) record = GeoRecord() @@ -364,32 +408,3 @@ class GeoRecordParser: return record - - - -def FindClosestBlock(blocks, target_coords): - """ - Find the closest block (from within blocks) to the GPS - coordinates given by target_coords. - """ - - # Empty by default. Hopefully we're passed some blocks. - closest_block = None - min_distance = 999999999.0 # Don't look at me like that. - - for block in blocks: - this_distance = GPS.CalculateDistance(target_coords, block.coordinates) - if (this_distance < min_distance): - closest_block = block - min_distance = this_distance - - return closest_block - - - -def FindAveragePopulationDensity(coords, geo_file_path): - grp = GeoRecordParser() - blocks = grp.parse_blocks(geo_file_path) - closest_block = FindClosestBlock(blocks, coords) - - return closest_block.population_density()