]> gitweb.michael.orlitzky.com - dead/census-tools.git/blobdiff - src/SummaryFile1.py
Added the linear program solving the midatlantic region.
[dead/census-tools.git] / src / SummaryFile1.py
index 3c82186474108e72908d3d3ded341d48470c1409..75868673aacd02b72482e506aaa665774e68466c 100644 (file)
@@ -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.
     """
 
 
 class GeoRecord:
     """
     This class wraps one record in an SF1 geo file.
     """
 
-    MinimumLineLength = 400
+    MINIMUM_LINE_LENGTH = 400
 
 
 class Block:
     """
 
 
 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):
     """
 
     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.
         # 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 = 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):
 
     def total_area(self):
-        return (self.area_land + self.area_water)
+        return (self.arealand + self.areawatr)
 
 
     def population_density(self):
 
 
     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:
 
     
 class GeoRecordParser:
@@ -80,12 +124,12 @@ class GeoRecordParser:
             try:
                 block = Block(record)
                 blocks.append(block)
             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 ValueError:
                 # A value couldn't be converted to the appropriate type.
                 continue
-            except InvalidAreaError:
-                # Something is funny with the geometry.
-                continue
             
         return blocks
 
             
         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.
         """
         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()
 
         
         record = GeoRecord()
 
@@ -364,32 +408,3 @@ class GeoRecordParser:
           
         
         return record
           
         
         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()