Lightweight Redistricting With OpenLayers Part II

Last month I posted about a lightweight redistricting prototype I threw together to test the concept. I thought I’d do another post now on some changes I managed to eek out over the holidays.

First and foremost, I had to de-ugly it. Normally I save the CSS lifting for the very end, but it was starting to creep me out.

Web Site PictureIn case you can’t tell, this is the pretty one.

Check out the pie chart – it shows not only republican/democratic majority, but the pie slice size represents the strength of the majority. I’d call it an infographic but I’m afraid that would necessitate buying an iPad and hanging around in coffee shops. That’s all Google Charts, which I highly recommend. Once you get past swearing at the cryptic URL arguments, it’s aces.

Now on to the code. First, I’m doing the voting precinct identify table on hover rather than on select. It makes a lot more sense that way – you can see what you’re getting before you add a voting precinct to a district. I’m using a custom callback on the SelectFeature control for that:

1
2
3
4
5
// Add selection control for marker layer
    selectControl = new OpenLayers.Control.SelectFeature(
  vectors,
  {onSelect: onFeatureSelect, stopSingle: false, clickout: true, toggle: true, callbacks: { 'over':onFeatureHover }}
    );

The onFeatureHover gets the feature as an argument, and from there we’re just setting table values to attributes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * On feature hover for precincts - populate ID table
 */
function onFeatureHover(feature) {
    // Set identify table
  $("#precinct").html(feature.attributes.precno);
  $("#population").html(addCommas(feature.attributes.population));
  $("#republican").html(addCommas(feature.attributes.republican));
  $("#democrat").html(addCommas(feature.attributes.democrat));
  $("#other").html(addCommas(feature.attributes.regother));
  $("#black").html(addCommas(feature.attributes.black));
  $("#white").html(addCommas(feature.attributes.white));
  $("#hispanic").html(addCommas(feature.attributes.hispanic));
}

Next up, saving plans. I’m using HTML5 localstorage to save the state of the data automatically every time the user reassigns a precinct. Since IE versions below 8 don’t know about localstorage, you have to do some detection here. IE 7 users can still use the site, it just won’t remember what they did between visits.

In the routine that updates the charts and summary table, I create a JSON object and populate it with precinct : district. Note when you store JSON in localstorage, you have to convert it to a string first via JSON.stringify (you have to love JavaScript naming conventions).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// JSON to go to localstorage
    var myjson = {};
// loop through vectors
    feats = vectors.features;
    for(i = 0; i < feats.length; i++) {
        feature = feats[i];

  ...

  // assign json
  myjson[feature.attributes.precno] = feature.attributes.cc;

    }

// Store results in local storage if available
    if (window.localStorage) {
  localStorage.setItem('redistricting_saved_plan', JSON.stringify(myjson));
    }

When the page is first loaded, you can look for localstorage from the last session and load the values. Note when we retrieve the JSON from localstorage, it’s a string – we have to do JSON.parse to turn it back into a JSON object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * Load vectors from geojson
 */
function loadGeoJSON() {
    vectors.removeAllFeatures();
    url = "data/cc.json";
    OpenLayers.loadURL(url, {}, null, function(r) {
        var p = new OpenLayers.Format.GeoJSON();
        var f = p.read(r.responseText);
        vectors.addFeatures(f);
  if (window.localStorage) {
      if (localStorage.getItem('redistricting_saved_plan')) {
      loadSavedPlan(JSON.parse(localStorage.getItem('redistricting_saved_plan')));
      }
  }
  updateValues();
    });
}


/**
 * Load saved plan from localstorage
 */
function loadSavedPlan(myjson) {
    feats = vectors.features;
    for(i = 0; i < feats.length; i++) {
        feature = feats[i];
  feature.attributes.cc = parseInt(myjson[feature.attributes.precno]);
  vectors.drawFeature(feature);
    }
}

And of course you’ll need to give users the option to start over if they completely screw things up. That’s just a matter of deleting the localstorage item if it exists and reloading the original GeoJSON file.

1
2
3
4
5
6
7
// Clear plan
    $("#clear-plan").click(function() {
  if (window.localStorage) {
      localStorage.removeItem("redistricting_saved_plan");
  }
  loadGeoJSON();
    });

I also added the ability to lock specific voting precincts. We can’t have a precinct a commissioner lives in up and move to a different district. That’s as easy as making an array of locked precincts and then making sure the user isn’t clicking on one with the jQuery inArray function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var lockedPrecincts = [207,132,62,71,39,236];

/**
 * Calculate CC of precinct on select and update values
 */
function onFeatureSelect(feature) {
    // Make sure CC isn't locked by an elected official living there.
    if (jQuery.inArray( parseInt(feature.attributes.precno), lockedPrecincts ) == -1) {
  // Set CC value
  cc = $('#district-assignment li.active').attr("id");
  cc = cc.replace("district-","");
  feature.attributes.cc = parseInt(cc);
  vectors.drawFeature(feature);

  // Calculate values
  updateValues();
    }
}

There were lots of other tweaks and clean ups, but that was the interesting stuff. The big Submit Plan button becomes enabled when the population balance meets a specific threshold. It doesn’t do anything, but boy can you click on it.

This is probably where things will sit until our Board of Commissioners makes a plan for redistricting. There’s an off chance the board will decide to ignore precinct lines, in which case this 20 hours or so of work will be hitting the trash can with a resounding thud. I can live with tossing out 20 or so hours; much more than that will chafe. My dream scenario is we’ll release this site to the public so any citizen can submit a plan, something I haven’t seen anybody else do. But that would also be up to the board.

In any event, if this project moves forward I’ll do a part III with the complete project, including server-side code for plan submission, and release all the code under an open source license.