Building CampaignHawk: Mapbox and Data (Part 9)

It’s time to get started with a map. I decided on Mapbox because I’ve used them before and they have great documentation. Fortunately there is a Meteor package that makes Mapbox integration super easy.

Step 1: Add Mapbox

First thing is signing up with Mapbox and getting a map ID and an access token. I’m going to skip over how to do this because it’s tedious. Once you have them, you’ll want to keep them in a settings.json file which you’ll need to create in your root directory. This is a Meteor best practice and should be done any time you have keys in your app.

Your settings.json file should look something like the code below. We need access to the key on our frontend, so we need to pass it as a public setting:

{
"public": {
"accessToken": "pk.fdfj920n90r24n9qf09020enf.f3nua9of32bie2",
"mapId": "samcorcos.n3u2n2"
}
}

Those are not real keys; you will need to use your own. The next thing you should probably do is add a .gitignore file to your root directory that contains the following code so your settings.json file is not publicly exposed in your Github repo. That file will contain the following:

settings.json

NOTE: When you have a settings.json file and you want to run Meteor with those settings, you have to start Meteor with the following command:

$ meteor --settings settings.json

Now we need to add Mapbox. Fortunately there is a Meteor package that makes this really easy. You can add it with just one command:

$ meteor add pauloborges:mapbox

Then we need to render the map on the screen, which we can do by adding the following to the top of our Map.jsx file:

Meteor.startup(function() {
Mapbox.load();
});
Tracker.autorun(function () {
if (Mapbox.loaded()) {
L.mapbox.accessToken = Meteor.settings.public.accessToken;
var map = L.mapbox.map("map", Meteor.settings.public.mapId);
}
});

This function adds the map to a div with an id of map, so let’s add one of those to our Map component within our content-wrapper:

<div id="map" className="mapbox"></div>

Step 2: Styling

Then we should add a style to our map to make sure it displays properly.

#map.mapbox {
position: absolute;
top: 0;
bottom: 0;
z-index: 0;
width: calc(100% - 60px);
}

You’ll also notice that our sidenav no longer has that nice shadow. Let’s change the z-index to bring that back to the front:

.sidenav {
z-index: 2;
...

The map also breaks our modal, which hides behind the map when you launch it. You’d think that all you have to do is change the z-index of our modal container, but there’s a little more to it than that. The z-index property only works on positioned elements (relative, absolute, fixed), so we need to position it first.

Within modal-active-darken, remove the height property, change the width value, and add the following:

position: absolute;
top: 0;
bottom: 0;
z-index: 1;
width: calc(100% - 60px);

At this point, the app looks like the image below.

And that’s pretty much all we need to do at this point for the map. The next thing we need is data.

Step 3: Get Data

I was graciously given some test data to play with from Tech Roanoke. They have a REST API and they have given me access to some dummy data that they use on their test server.

The data we’re going to use covers about 4000 voters in Washington state.

They also said I could publish the access key, so you can play around with the data as well if you’d like. The key is: CERSXGSD

I’m going to store this value in settings.json and since this is a server-only value, I’m not going to store it under public:

"accessCode": "CERSXGSD"

We’re going to want to store this in a database so we don’t have to make this call every time we make a change. Fortunately, this is super easy to do with Meteor. Within collections.js, add the following to create a new Mongo collection:

VoterData = new Mongo.Collection('voterData');

And that’s it! So now we need to add HTTP so we can get this data:

$ meteor add http

The first thing we need to do is send a POST request to get a Json Web Token (JWT) that we can then use to access the API. We’re going to make the call in our server.jsx file, and it will look something like this:

HTTP.post("http://trc-backend-test.azurewebsites.net/login/code?code=" + Meteor.settings.accessCode)

Then we need to send a GET request to receive the data that we want. We will have to call this within the callback from our POST request, which returns Server, SheetId, and AuthToken. That will look something like this:

HTTP.get(res.data.Server + 
"/sheets/" +
res.data.SheetId +
"?type=json", {
headers: {
"Authorization": "Bearer " + res.data.AuthToken,
"Accept": "application/json; charset=utf-8"
}
})

We also want to wrap this in a Meteor.startup and only run it when the collection is empty. That will look something like this:

Meteor.startup(function() {
if (VoterData.find().count() === 0) {
...
}
})

Each voter has a set of characteristics, listed in the image below.

Unfortunately, the response we receive lists out all of the properties in an ordered list, so we have to join them together. For example:

{
FirstName: ["Bob", "Sally", ...],
LastName: ["Jackson", "Williams", ...]
...
}

This is not what Mongo was built for, but we only have to do this once per campaign, so it isn’t the end of the world. So what we’re going to have to do is go over each item in the list, and then go over each key to make a voter and insert it into the database:

var keys = _.keys(res.data);
_.each(res.data.RecId, function(item, i) {
var voter = {};
_.each(keys, function(key) {
voter[key] = res.data[key][i]
})
VoterData.insert({
rec_id: voter.RecId,
first_name: voter.FirstName,
last_name: voter.LastName,
birthday: voter.Birthday,
gender: voter.Gender,
city: voter.City,
zip: voter.Zip,
address: voter.Address,
lat: voter.Lat,
long: voter.Long,
party: voter.Party,
history: voter.History,
precinct_name: voter.PrecinctName
});
})

So all together, our data-gathering function looks like the code in this link. There’s too much code for me to paste below…

And now we have data on 4000 voters in Washington state!

Next Steps

Now that we have this data, we need to figure out what to do with it. The first thing we should probably figure out is the Republican-Democrat data layer for the map. We should also figure out how this data is structured to make more sense of it

Bonus?

A friend of mine who is a D3 wizard has agreed to use this data to make some fancy charts. I’ll see if I can convince him to blog about the process.