Saving the world with Laravel & Cron

Saving the world with Laravel & Cron

Open data can warn people when it’s too late to save their homes from an earthquake. I made it work in Bath, here’s how to protect your town.

The spec: To create an alert system that detects significant earthquakes reported by the US Geological Survey and determine the local threat level.

Tooling: PHP, Laravel 4, MySQL and Cron

1. Scaffold the database

USGS reports come with a handy unique alphanumeric identifier, plus the location and some indicators of the quake’s strength.

You may not be a quakologist, so here are the three key numbers we want: You’ve probably heard of magnitude (the richter thing) but that only tells us how much of a bang the quake made.

Since a big bang in the middle of nowhere is less worrisome than a medium bang in your local Lidl, we should also look at CDI – or the reported quake intensity. This one tells us if things are falling down. The last one is the tsunami alert – or whether we should worry about tidal waves up the Avon.

From the Artisan CLI type php artisan migrate:make create_quakes_table to scaffold up a database migration. Then make yourself a table, mine looks a bit like this:

public function up()
{

    Schema::create('quakes', function($table)
    {

        // Create fields
        $table->engine='InnoDB';
        $table->string('id', 100)->unique();
        $table->decimal('cdi', 3, 1)->unsigned()->nullable()->default(NULL);
        $table->decimal('mag', 3, 1)->nullable()->default(NULL);
        $table->decimal('latitude', 16, 12)->nullable()->default(NULL);
        $table->decimal('longitude', 16, 12)->nullable()->default(NULL);
        $table->string('place', 255);
        $table->boolean('tsunami')->unsigned()->default(false);
        $table->string('url', 255);
        $table->timestamps();

        // Create indexes
        $table->primary('id');

    });

}

public function down()
{
    Schema::dropIfExists('quakes');
}

Run php artisan migrate and we’re in business. We now have somewhere to put our disasters.

2. The Artisan quake scraper

The next step is to write an Artisan command that will scrape the latest USGS feeds for earthquake events. I picked the Past 30 days significant earthquakes feed because I don’t really care about small tremors and it is updated when event information is clarified.

Scaffold up a command with php artisan command:make QuakesGet from the Artisan CLI. Then pop in the following scraper magic:

protected $name = 'quakes:get';

protected $description = 'Get latest earthquakes data from USGS.';

public function __construct()
{
    parent::__construct();
}

/**
 * Execute the console command.
 */
public function fire()
{

    try
    {

        $url = 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/significant_month.geojson';

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $r = curl_exec($ch);
        curl_close($ch);

        $quakes = json_decode($r);

        foreach ($quakes->features as $quake)
        {

            if ($quake->properties->type == 'earthquake')
            {

                // Attempt to find the quake
                $q = Quake::where('id', '=', $quake->id)->first();
                if (is_null($q))
                {
                    $q = new Quake;
                }

                $q->id = $quake->id;
                $q->cdi = $quake->properties->cdi;
                $q->mag = $quake->properties->mag;
                if ($quake->geometry->type == 'Point')
                {
                    $q->latitude = $quake->geometry->coordinates[1]
                    $q->longitude = $quake->geometry->coordinates[0];
                }
                $q->place = $quake->properties->place;
                $q->tsunami = ($quake->properties->tsunami == 1);
                $q->url = $quake->properties->url;
                $q->save();

            }

        }

        $this->info(count($quakes->features) . ' quakes parsed.');
        Log::info('Latest quakes extracted. Aaaaghhhh!');

    }
    catch (Exception $e)
    {

        $this->error('Failed to get quake data. Reason for fuck up: ' . $e->getMessage());
        Log::error($e);

    }

}

Note the command has no arguments or options. Finish up by registering your command in app/start/artisan.php

Artisan::add(new QuakesGet);

Boom! We’re ready to roll. Poll the latest quakes reports by running php artisan quakes:get from the command line and the latest events magically appear in your database.

At this point your may want to finesse your code and build some views on the data. You can see mine at The Bath Quake Alert Centre.

Now to get this happening automatically…

3. The Cron disaster detector

I use Cron to schedule regular tasks so the final step is to fire our Artisan command regularly by registering it in your crontab. I found every 5 minutes is enough to stay on top of the disaster:

*/5 * * * * /usr/bin/php-5.4-cli /var/sites/w/webunknown.co.uk/artisan quakes:get

Protip for Vidahost cloud users: Use php-5.4-cli to run PHP jobs from your crontab because this is the one that works.

Plug in Twitter to spread fear?

Twitter is my platform of choice for unending misery so I further developed the Artisan command to auto-tweet new events with a suitably scary message. Go crazy and write in big letters, I even added twitter cards with doomsday images.

For a nice way to play with the Twitter API and send menacing tweets, I can highly recommend John Thuau’s package.

The results

The Bath Quake Alert Centre has been running for a couple of months and Bath has not seen a single apocalypse since I installed the service. It’s a highly effective piece of code.

Upper Bristol Road, BathNot on my watch: Upper Bristol Road, Bath

If you’d like to use this code in your town, please copy it and treat as your own. You can also use it to laugh at your Ruby friends who are probably still setting up.

Play Apocalypse Bath Laravel Mapping Open data PHP