Dot density calculations

A dot density map shows concentrations of subjects within a given area. If an area is divided into polygons containing statistical information, you can model that information using randomly distributed dots within that area using a fixed ratio across the dataset. This type of map is commonly used for population density maps. The cat map in Chapter 1, Learning Geospatial Analysis with Python, is a dot density map. Let's create a dot density map from scratch using pure Python. Pure Python allows you to work with much lighter weight libraries that are generally easier to install and are more portable. For this example, we'll use a U.S. Census Bureau Tract shapefile along the U.S. Gulf Coast, which contains population data. We'll also use the point in polygon algorithm to ensure that the randomly distributed points are within the proper census tract. Finally, we'll use the PNGCanvas module to write out our image.

The PNGCanvas module is excellent and fast. However, it doesn't have the ability to fill in polygons beyond simple rectangles. You can implement a fill algorithm, but it is very slow in pure Python. However, for a quick outline and point plot, it does a great job.

You'll also see the world2screen() method similar to the coordinates-to-mapping algorithm we used in SimpleGIS in Chapter 1, Learning Geospatial Analysis with Python, as shown here:

import shapefile
import random
import pngcanvas

def point_in_poly(x,y,poly):
    """Boolean: is a point inside a polygon?"""
    # check if point is a vertex
    if (x,y) in poly: return True
    # check if point is on a boundary
    for i in range(len(poly)):
        p1 = None
        p2 = None
        if i==0:
            p1 = poly[0]
            p2 = poly[1]
        else:
            p1 = poly[i-1]
            p2 = poly[i]
        if p1[1] == p2[1] and p1[1] == y and 
        x > min(p1[0], p2[0]) and x < max(p1[0], p2[0]):
            return True      
    n = len(poly)
    inside = False
    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y
    if inside: return True
    else: return False

def world2screen(bbox, w, h, x, y):
    """convert geospatial coordinates to pixels"""
    minx,miny,maxx,maxy = bbox
    xdist = maxx - minx
    ydist = maxy - miny
    xratio = w/xdist
    yratio = h/ydist
    px = int(w - ((maxx - x) * xratio))
    py = int((maxy - y) * yratio)
    return (px,py)

# Open the census shapefile
inShp = shapefile.Reader("GIS_CensusTract_poly")
# Set the output image size
iwidth = 600
iheight = 400
# Get the index of the population field
pop_index = None
dots = []
for i,f in enumerate(inShp.fields):
    if f[0] == "POPULAT11":
      # Account for deletion flag
      pop_index = i-1
# Calculate the density and plot points
for sr in inShp.shapeRecords():
    population = sr.record[pop_index]
    # Density ratio - 1 dot per 100 people
    density = population / 100
    found = 0
    # Randomly distribute points until we
    # have the correct density
    while found < density:
        minx, miny, maxx, maxy = sr.shape.bbox
        x = random.uniform(minx,maxx)
        y = random.uniform(miny,maxy)
        if point_in_poly(x,y,sr.shape.points):
            dots.append((x,y))
            found += 1
# Set up the PNG output image
c = pngcanvas.PNGCanvas(iwidth,iheight)
# Draw the red dots
c.color = (255,0,0,0xff)
for d in dots:
    # We use the *d notation to exand the (x,y) tuple
    x,y = world2screen(inShp.bbox, iwidth, iheight, *d)
    c.filled_rectangle(x-1,y-1,x+1,y+1)
# Draw the census tracts
c.color = (0,0,0,0xff)
for s in inShp.iterShapes():
    pixels = []
    for p in s.points:
        pixel = world2screen(inShp.bbox, iwidth, iheight, *p)
        pixels.append(pixel)
    c.polyline(pixels)
# Save the image  
img = open("DotDensity.png","wb")
img.write(c.dump())
img.close()  

This script outputs an outline of the census tract with the density dots to show population concentration very effectively:

Dot density calculations
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset