Sending Location Adjustments from iOS to CouchDB

by Matt Blair on March 1, 2011

I’ve been a fan of Portland’s Public Art collection since I first visited the city twelve years ago. Over the last few months, I’ve been working with the Mayor’s office and the Regional Arts & Culture Council to build a map-based iOS app that showcases this art and presents it to a wider audience.

And now, it’s here: Public Art PDX is available for download from the App Store.

Update: Don’t miss the launch video the Mayor’s office put together!

Over the next few weeks, I’ll be blogging about various technical aspects of the project. In this post, I’m going to go into detail about collecting location adjustments from iOS devices.

Where is it, exactly?

The public art data, originally gathered by RACC, was released by the city of Portland as part of the Civic Apps Challenge.

Each record had been automatically geo-coded by address. In many cases, this meant that the coordinates in the database were hundreds or even thousands of meters away from the actual location of the works of art:

An arrow shows the distance between the geo-coded location and actual location, which is just over a kilometer.

Just over a kilometer

The distance between the original geo-coded location of this sculpture and its actual location is just over a kilometer (about two thirds of a mile) as the crow flies, and more than twice that if you walk or drive the road between the two.

With over 400 works of art in the initial dataset, and hundreds more on the way, neither I nor the agencies involved have the capacity to visit and verify all these locations — but our audience does.

In each of my open data projects, I’ve started with the question: How can we put this data into the hands of citizens and visitors in a way that is compelling, engaging and participatory? Crowd-curation of location seemed like a great way to get people actively involved in the initial release of this particular app.

Today’s mobile devices allow someone using an app to send in very accurate locations without ever seeing coordinate values or ID numbers.

In the case of location adjustments, this meant that I could give app users a way to submit corrections to the dataset without ever exposing them to the underlying complexity of UUIDs, negative longitude, significant digits, etc. These kinds of things don’t intimidate technical users, but they could be a barrier to participation for many people who will use this app.

The mobile device is only half the story, though: It doesn’t matter how easy it is to send data if you can’t receive and manage it. That’s where Couch comes in.

Building an API

I’ve used Django to build web and intranet applications in the past, but it does not support building RESTful APIs natively. While working on PDX Trees I found the most popular third-party module for REST unmaintained and problematic, especially for submitting data with POST requests. I got it to work, after lots of troubleshooting and patching, but it was not an experience I was eager to repeat. I don’t like deploying duct-taped code that might break when installing the next security patch or minor framework release.

I learned a little about CouchDB (and GeoCouch) while using Max Ogden’s PDXAPI, and decided to give it a try.

CouchDB is a document database that stores each document as JSON, and makes them available via HTTP verbs like GET and POST. Making an API to receive data is simple: you just create a new database, configure any read-write permissions you need, and submit JSON to it. It’s that easy.

For example, posting data from the command line using curl looks like this:

curl -d '{"aKey": "aValue" }' --header "Content-Type: application/json"
     -vX POST http://youraccount.couchone.com/yourdb

Or, if you have JSON data in a text file:

curl -d @some-data.json --header "Content-Type: application/json"
     -vX POST http://youraccount.couchone.com/yourdb

Couch DB has been used in a wide variety of production deployments, it is easy to backup through replication and it adapts easily to changing data and metadata needs. I’m using  CouchBase for this project, which is excellent in terms of scalability and maintenance. I’m a big fan of hosted open-source solutions: letting experts manage the server allows me to focus on my data and how I present it in the app.

The iOS Interface

Now that I had a cloud-based service ready to receive data, I needed to provide a way for people to make the adjustments and send them.

When the app displays details about a specific work of art, it adds a map marker icon next to the location information if the coordinates in the database have not been verified:

Ghost Ship Detail View, with a pin icon next to the location information

Ghost Ship Detail View, with adjustment icon in lower right

If an app user taps that icon, they see a new map view, with a pin in the original geo-coded location of the art and a blue marker showing their current location:

Original and Current Location of Ghost Ship

Original and Current Location

A thumbnail of the work of art appears in the upper left of the screen for reference. The user drags the pin to the actual location of the work of art:

Pin moved to accurate location

Pin moved to accurate location

And then taps the Save button. That’s it.

The app starts to send the information asynchronously, returns to the detail view and thanks the submitter once it receives a response from the server:

Acknowledging their Contribution: A thank you notice to the person adjusting the location

Acknowledging their Contribution

The App Code

Behind the scenes, this is relatively easy to implement in Objective-C.

First, the new coordinates are placed in an NSDictionary object that conforms to the GeoJSON format:

NSArray *coordinateArray =
     [NSArray arrayWithObjects:self.newLongitude,self.newLatitude,nil];
 
NSDictionary *geometryDict = [NSDictionary dictionaryWithObjectsAndKeys:
     coordinateArray,@"coordinates",
     @"Point",@"type",
     nil];

Then I set up an NSDateFormatter for a timestamp:

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
 
// uses ISO 8601, as described here: http://www.w3.org/TR/NOTE-datetime
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"];

And grab the app version number:

NSString *versionNumber =
     [[[NSBundle mainBundle] infoDictionary]
     valueForKey:@"CFBundleShortVersionString"];

Now I’m ready to create an NSDictionary to hold all the information the app will submit, including the original location, the submitted location, ID numbers, and title, for easy reference in the admin view:

NSDictionary *locationUpdateDict = [NSDictionary dictionaryWithObjectsAndKeys:
   [theArt latitude],@"originalLatitude",
   [theArt longitude],@"originalLongitude",
   geometryDict,@"geometry",
   [theArt couchID],@"publicArtID",
   [theArt recordID],@"recordID",
   [theArt title],@"title",
   @"reported-location",@"type",
   @"submitted",@"reviewStatus",
   [NSString stringWithFormat:
        @"PublicArtPDX v%@ iOS app user", versionNumber],@"submittedBy",
   [dateFormatter stringFromDate:[NSDate date]],@"submittedDate",
   nil];
 
// And don't forget to let go of this:
[dateFormatter release];

Using the json-framework, it takes one line to convert this NSDictionary object into valid JSON:

NSString *jsonToSubmit = [locationUpdateDict JSONRepresentation];

Which looks like this:

{
     "originalLatitude": 45.513289,
     "originalLongitude": -122.668293,
     "geometry": {
     "coordinates": [
             -122.666736579521,
             45.51915887481938
         ],
         "type": "Point"
     },
     "publicArtID": "b916e524b2e1f24e72ac7a81aa530899",
     "recordID": "1839",
     "title": "Ghost Ship"
     "type": "reported-location",
     "reviewStatus": "submitted",
     "submittedBy": "PublicArtPDX v1.0.0 iOS app user",
     "submittedDate": "2011-02-12T15:13:57-0800"
}

And then just a few more lines to set up and submit a POST request to CouchDB using the ASIHTTPRequest library:

// kURLForUpdates is a constant that points to
// the CouchDB database, e.g.:
// http://myinstance.couchone.com/mydatabase
 
NSURL *theURL = [NSURL URLWithString:kURLForUpdates];
 
self.locationSubmitRequest = [ASIHTTPRequest requestWithURL:theURL];
 
[[self locationSubmitRequest] setRequestMethod:@"POST"];
 
[[self locationSubmitRequest] addRequestHeader:@"Content-Type"
                                         value:@"application/json"];
 
[[self locationSubmitRequest] appendPostData:[jsonToSubmit
                           dataUsingEncoding:NSUTF8StringEncoding]];
 
[[self locationSubmitRequest] setDelegate:self];
 
[[self locationSubmitRequest] startAsynchronous];

To limit submissions to certain accounts, I can authenticate the POST request by adding two more lines of code that define the username and password for the request object before starting it:

[[self locationSubmitRequest] setUsername:kUpdateUsername];
[[self locationSubmitRequest] setPassword:kUpdatePassword];

In other projects under development, I want app users to be able to submit private information. CouchBase can handle that, too: switch the URLs to https, and submissions are encrypted en route.

I’m making the request asynchronously so I don’t lock up the main thread. Even though it’s a tiny chunk of data, it can still take a few seconds over cellular, especially if only 2G is available.

You may have noticed that the request is actually a property of the view controller. Why? So I can stop the request if I need to.

If the app dismisses a view that initiated an asynchronous request while the request is still running, the app will crash when the response tries to send a callback message to its delegate.

There are a few scenarios in which the request needs to be stopped: if the user gets tired of waiting and decides to cancel their submission or leave the app, or if the Reachability changes because the cellular connection is lost.

In any of these cases, I can call this method to handle the situation gracefully:

- (void)killRequest {
 
     if ([[self locationSubmitRequest] inProgress]) {
 
         [[self locationSubmitRequest] cancel];
 
         // update UI, if needed
         [MBProgressHUD hideHUDForView:self.view animated:YES];
 
     }
 
}

If the data involved is mission-critical, a method likes could be used to store it for submission later.

Within Couch

In my admin interface, I can view the original location and compare it to the submitted location. If the adjustment makes sense, I push it to the production CouchDB database. The review UI uses a mix of HTML and Javascript for viewing and mapping data and Python with CouchDBKit for moving data between databases. I’ll save details about that process for another post.

Once the scripts have migrated the new location to the production Couch database of public art, it will be rolled into the next datastore release for Core Data on the iPhone.

The app checks a document in CouchDB once a day, and downloads the datastore if it’s newer than what’s on the phone. CouchBase can store and serve the binary file for the data update, too.

In Summary

With CouchDB, I was able to implement a RESTful API to accept data sent from mobile apps in an order of magnitude less time than it would take me to develop and deploy a traditional web application and add a RESTful API to it.

In this particular project, I did not need a public, polished web interface to present the data, I only needed an API to receive data. I was able to do that in three simple steps: adding a database in CouchDB, configuring write access, and submitting JSON via authenticated POST requests. It’s really that easy.

I’m using Couch in other projects which do have web interfaces, including dynamic maps which send geo-queries to CouchDB, like this beta version of the Portland Poetry Posts project.

That project will eventually have an iOS app in addition to the website, with CouchDB handling data for both interfaces, as well as any other apps that other developers build around it.

I’m regularly moving data between CouchDB databases and the web via Javascript, legacy relational databases and text files via Python, and iOS devices via Objective-C. It feels almost native from each language because of the simplicity and flexibility of using HTTP verbs like GET and PUT with JSON payloads.

I’ll cover other aspects of using CouchDB and explain some of the design decisions I made while working on the public art app in future posts.

Until then, enjoy the app — and please let me know what you think!

{ 4 comments… read them below or add one }

davidalm March 1, 2011 at 4:17 pm

I discovered your post via following RWW on twitter.

Anyway, I was just about to dig into my first Django project that needs a REST API — glad I read your post! Is there really no good solution?

Would you recommend any particular web framework (Django? Rails 3?) to build web front-ends to CouchDB? Then again, Rails is supposed to be so good for REST, why not just Rails…? Any reply much appreciated! (I’ll be back to read your ObjC code in a few weeks.)

Matt Blair March 3, 2011 at 7:39 am

I was really jealous the first time I saw a code sample of how easy it is to implement REST in Rails! If you’re already familiar with Rails, that might be a good option.

I haven’t looked at the REST options for Django in nearly six months, so I can’t provide a definitive answer on that. I’d suggest looking at django-tastypie. It worked fine for GETs when I tried it, but I ran into some problems formatting binary data on the phone for POSTs in a way that made tastypie happy. I wouldn’t be surprised if that’s easier now, because it is an actively-maintained project and the developers were helpful and responsive to questions.

I know there are some people using Django to present a front-end for CouchDB. I’d check out CouchDBKit which has an extension for Django. CouchDBKit is solid, and simplifies a lot of the details of interacting with data in Couch. Not sure about Rails/Couch integration.

Jonathan Wight April 17, 2011 at 1:17 pm

You should probably check out Trundle: It’s a ObjC CouchDB API – https://github.com/schwa/trundle

Matt Blair April 23, 2011 at 11:05 am

Yes, I plan to check it out for a Couch-related project I’m starting soon. I saw it in the iOSCouchbase example project, and it looks great, but I haven’t used it yet.

Leave a Comment