mihasya / Services_SimpleGeo

A PEAR package for SimpleGeo's API

Home Page:http://simplegeo.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Introduction

Considering one of our cofounders was an active PEAR contributor, worked for Digg, and was on the governing board for PEAR, we'd be remiss if we didn't create a competent PHP library to compliment SimpleGeo's API. As a result, Joe spent some time coding up a PEAR client and releasing it publicly on GitHub.

Requirements

Installation

The code Joe wrote is a PEAR package, but isn't available via the normal PEAR installation process. We currently don't have any plans to build a non-PEAR package, but one could be quickly built with the PHP OAuth client library. This describes how to install the package locally if you have access to installing packages via the pear command line utility:

  1. Install the requirements.
    • pear install HTTP_Request2
    • pear install HTTP_OAuth
    • pear install Net_URL2
  2. Check out the GitHub repository.
    • git clone git://github.com/simplegeo/Services_SimpleGeo.git
    • cd Services_SimpleGeo
    • pear package
    • sudo pear install Services_SimpleGeo-x.y.z.tgz (Replace x.y.z with whatever version you've checked out).

PEAR and Your include_path

If you haven't used PEAR before you should check it out. Many shared hosts provide mechanisms for using PEAR and it's home to thousands of lines of great code. The PEAR documentation covers how to install it and get it up and running.

Specifically, you'll want to ensure that PEAR is in your include_path. You can manipulate this manually in your own code using PHP's set_include_path function. Just make sure that the path to the Services directory in the Services_SimpleGeo package is in your include_path and you should be good to go.

Brief Overview of Client Features

The client is based on HTTP_OAuth and HTTP_Request2. It handles all of the HTTP and OAuth internally and exposes easy to use methods for interfacing with the various API endpoints.

When it sends a request to the API is will inspect the response and handle it appropriately. This means that if an error is returned from the API it will throw a Services_SimpleGeo_Exception with the error code and error message in it, which are accessible via the exception's getCode() and getMessage() methods.

This also means that it will automatically JSON decode any response from the API. Depending on the method you're using the client will throw an exception, return true on success, or return a decoded response as a PHP stdClass. Internally the client uses PHP's json_decode function.

Creating an Instance of the Client

You'll need to instantiate an instance of the Services_SimpleGeo client before you can do much of anything with the API. To do so you need to require_once 'Services/SimpleGeo.php in a PHP script. Ensure that PEAR is in your path, as we describe above. If you're having trouble finding your OAuth token and secret, please see our FAQ on the subject.

<?php

// Require the PEAR client library. No other requires are needed.
require_once 'Services/SimpleGeo.php';

// If you log into SimpleGeo and click on Account you'll see 
// your OAuth token and secret.
$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f',
    '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7');

print_r($client->getAddress(40.016983, -105.27753));

?>

The above code instantiates the client and uses the getAddress() method, which converts a latitude and longitude into a human readable address (Only works in the US as of now). The code above should return the following stdClass, which is a decoded GeoJSON payload.

stdClass Object
(
    [geometry] => stdClass Object
        (
            [type] => Point
            [coordinates] => Array
                (
                    [0] => -105.27753
                    [1] => 40.016983
                )

        )

    [type] => Feature
    [properties] => stdClass Object
        (
            [state_name] => Colorado
            [street_number] => 1354
            [country] => US
            [street] => Walnut St
            [postal_code] => 80302
            [county_name] => Boulder
            [county_code] => 013
            [state_code] => CO
            [place_name] => Boulder
        )

)

Please keep in mind that getAddress() is based on TIGER data and can be a little off depending on the exact placement of the latitude and longitude depending on their relation to the address. It's very, very accurate down to the street block, city, state, and zip code though.

Creating a Record

The PEAR library includes a class called Services_SimpleGeo_Record that is an abstract interface for manipulating records within the API. Below is an example of instantiating a client, creating a record, sending it to the API and checking the return code to ensure all is well.

<?php

require_once 'Services/SimpleGeo.php';

$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f',
     '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7');

// The constructor requires you specify the layer you wish
// to insert the record into, the unique ID for that record,
// and it's latitidue and longitude. NOTE: You can also
// specify an object type and when it was created. If you do
// not specify a type it defaults to 'object' and created
// defaults to the time the record is added to the API.
$record = new Services_SimpleGeo_Record('com.example.somelayername',
    'a-unique-id', 40.016983, -105.27753);

// Add the record to the API. If all goes well you'll get a
// boolean true back. If something blows up it will throw
// an exception.
try {
    $result = $client->addRecord($record);
    if ($result === true) {
        // Remember that the API is eventually consistent so it might take
        // a second or two before the record is available throughout the
        // API ecosystem.
        echo "All appears to be well.\n";
    }
} catch (Services_SimpleGeo_Exception $e) {
    echo "ERROR: " . $e->getMessage() . " (#" . $e->getCode() . ")\n";
}

?>

A successful API response from adding a record would be a 202, which means we've received it for processing and it will be available throughout the API shortly. In our experience it generally never takes more than a second for the record to be available across all three data centers.

Deleting a Record

If you wanted to delete the record you just inserted above you'd need to send an HTTP DELETE to that record's URI within the system. Thankfully, the PEAR client handles that for us. Below is an example of deleting a record from the API. Remember that it will return either true or throw a Services_SimpleGeo_Exception.

<?php 
       
require_once 'Services/SimpleGeo.php';  
    
$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f',
    '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7');
         
try {  
    $result = $client->deleteRecord('com.example.somelayername',  
        'a-unique-id'); 
    
    if ($result === true) {  
        // Remember that the API is eventually consistent so it might take 
        // a second or two before the record is deleted throughout the   
        // API ecosystem.  
        echo "I have pwned your record.\n";     
    }    
} catch (Services_SimpleGeo_Exception $e) {    
    echo "ERROR: " . $e->getMessage() . " (#" . $e->getCode() . ")\n";   
} 
      
?>

Inserting Records in Batch

Using GeoJSON's FeatureCollection format you can input up to 100 records at a time into the API. This is a great way to batch load larger data sets. It should be noted that all writes to SimpleGeo go through a queue so you might see a greater lag when inserting a large number of records using this method. As an example, we've found we can comfortably load 20,000 records every few minutes with a few minute breather.

To add records in bulk you need to create an array of Services_SimpleGeo_Record instances and then use the client's addRecords() method. Note that this method is the plural form of the singular addRecord() method.

<?php 
                                                                             
require_once 'Services/SimpleGeo.php'; 
                                                                             
$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f', 
    '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7'); 
                                                                             
$joe = new Services_SimpleGeo_Record('com.example.myusers', 'joestump', 
    40.013212, -105.292814) 
$jon = new Services_SimpleGeo_Record('com.example.myusers', 'jonstump', 
    40.0073646, -105.2533779) 
                                                                             
try { 
    // The addRecords() method takes an array of instances of 
    // Services_SimpleGeo_Record. 
    $result = $client->addRecords(array($joe, $jon)); 
                       
    if ($result === true) { 
        echo "I just added your two records.\n"; 
    } 
} catch (Services_SimpleGeo_Exception $e) { 
    echo "ERROR: " . $e->getMessage() . " (#" . $e->getCode() . ")\n"; 
}
                                                                             
?>

Querying for Nearby Records

Once you've added a few records to a layer of yours you're probably going to want to query for records that are near your user. This is the basic use case that SimpleGeo was created for after all. In the PEAR client there are two ways that you can query for nearby records.

  • By latitude and longitude with a radius.
  • By geohash.

The getNearby() takes a single string argument that should either be a $latitude,$longitude (e.g. 42.0896429,-84.187606) or a geohash (e.g. dpkpkq0myw55e). Note that there is no space between the comma and the latitude and longitude.

<?php 
                  
require_once 'Services/SimpleGeo.php';
                 
$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f',
    '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7');
             
try {
    $result = $client->getNearby('com.simplegeo.global.twitter',
        '40.013212,-105.292814', array('limit' => 5, 'radius' => 2));
    print_r($result);
           
    $result = $client->getNearby('com.simplegeo.global.twitter', '9xj5sh');      
    print_r($result);
} catch (Services_SimpleGeo_Exception $e) {
    echo "ERROR: " . $e->getMessage() . " (#" . $e->getCode() . ")\n"; 
} 
  
?>

The first example above shows querying the API with a latitude and longitude pairing along with passing the radius and limit arguments. The second argument is a simple geohash example.

Fetching a Record by ID

To fetch a single record you can use the getRecord() or getRecords() methods. Both take a layer name as the first argument and either a single record ID or an array of record IDs for the second argument. If we wanted to fetch the joestump record from above, we'd use the following code. The API will respond with a 404 if the record is not found in the system.

<?php
      
require_once 'Services/SimpleGeo.php';
          
$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f',
    '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7');
       
try {
    $result = $client->getRecord('com.simplegeo.global.twitter', 9073364877);
    print_r($result);
           
    $result = $client->getRecords('com.simplegeo.global.twitter',
        array(9073757897, 9073313347, 9073364877));
    print_r($result);
} catch (Services_SimpleGeo_Exception $e) {
    echo "ERROR: " . $e->getMessage() . " (#" . $e->getCode() . ")\n";
}
 
?>

The first call fetches a single record by its ID and the second fetches an array of records by IDs. If you are needing to fetch multiple records it makes much more sense to fetch using the getRecords() method to reduce HTTP overhead and reduce the number of calls you make to the API.

Fetching a Record's History

One of the more interesting features of SimpleGeo's API is that we keep track of where a record has been over time. Every time a record is updated we take the record's previous postion, along with the time it was at that point, and push it into its history. This allows you to ping the API and see where a point has been throughout its history in the system.

This feature is useful for creating GPS tracks, breadcrumbs, and other historical traces you need to make for records over time and space.

<?php
      
require_once 'Services/SimpleGeo.php';
          
$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f',
    '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7');
       
try {
    $result = $client->getHistory('com.example.myusers', 'joestump');
    print_r($result);
} catch (Services_SimpleGeo_Exception $e) {
    echo "ERROR: " . $e->getMessage() . " (#" . $e->getCode() . ")\n";
}
 
?>

If my application had been updating joestump's location over time, then I'd be able to get a history of where joestump has been over time. It could also be used to track, say, truck 5940 while it's out for deliveries.

Find all polygons that a given point contains

The getContains() function takes a latitude and longitude, and returns all of the polygons or boundaries containing that point.

Let's find all of the polygons associated with this point, near Downingtown, PA:

<?php
  
require_once 'Services/SimpleGeo.php';
      
$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f',
    '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7');

$lat = 40.0064958
$lon = -75.7032742;

try {
    $result = $client->getContains($lat, $lon);
    print_r($result);
} catch (Services_SimpleGeo_Exception $e) {
    echo "ERROR: " . $e->getMessage() . " (#" . $e->getCode() . ")\n";
}

?>

In the returned response, you will see Country, City, Province, County, Urban Area, Neighborhood, Postal Code, Census Tract and a few others, depending on the geography of your query. Check out our endpoints page for more info.

Find all overlapping polygons and boundaries for a given bounding box

You can use the getOverlaps() function to see which polygons overlap a bounding box. The function takes four parameters: south, west, north, east. *These are single units of latitude and longitude. Let's explore and example. I'm using the metropolitan area of Grosse Pointe, Michigan:

<?php

require_once 'Services/SimpleGeo.php';
  
$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f',
    '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7');

$south = 42.4166  // Grosse Pointe Farms
$west = -82.9189  // Harper Woods
$north = 42.4465  // Grosse Pointe Woods
$east = -82.8753  // Grosse Pointe Shores

...

Notice how each value is a single latitude or longitude. Instead of referencing a point, we are referencing a line on the map. Four of these makes a bounding box:

bounding-box.png

A great tool for finding latitudes and longitudes on a map is called http://getlatlon.com.

Now let's call the getOverlaps() function:

try {
    $result = $client->getOverlaps($south, $west, $north, $east);
    print_r($result);
} catch (Services_SimpleGeo_Exception $e) {
    echo "ERROR: " . $e->getMessage() . " (#" . $e->getCode() . ")\n";
}

?>

In the returned response, you will see roughly 26 polygons, including three city boundaries, three zip codes, and a few Census tracts. For more information on the overlaps/ endpoint, check out the endpoints page.

Find the boundary of a polygon

If you tried the previous example, you will have noticed the id value 'id': 'County:Wayne:dpsb93'. You can use the getBoundary() function to find the lat/lon boundaries of a given polygon. Here's how it works:

<?php

require_once 'Services/SimpleGeo.php';

$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f',
    '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7');

$id = "County:Wayne:dpsb93";  // Wayne County, Mich.

try {
    $result = $client->getBoundary($id);
    print_r($result);
} catch (Services_SimpleGeo_Exception $e) {
    echo "ERROR: " . $e->getMessage() . " (#" . $e->getCode() . ")\n";
}

?>

In the return response, you will see a valid GeoJSON feature object with all of the lat/lons of that polygon. In this case, 2,616 values were returned in the geometry block. More info on this function on the endpoints page.

Fetching SpotRank Population Density

We've partnered with Skyhook Wireless to provide access to their amazing SpotRank data. This data provides crowd-sourced population data for about 15% of the globe – mostly in metropolitan areas.

One thing to note is that this endpoint returns GeoJSON Polygon's and not Point's like much of the rest of the API. Additionally, GeoJSON Polygon's can be a bit confusing as 5 coordinates are returned rather than a bounding box or just the four points. This is because GeoJSON specifies that you must draw a "ring" around the polygon starting with the most outer ring. You'll see that SpotRank returns tiles that are roughly 100 meters square and that the last point is identical to the first point, which closes the ring.

<?php

require_once 'Services/SimpleGeo.php';

$client = new Services_SimpleGeo('9374824de77edfde2836293dd28f9749f63cb17f',
    '7585d1f7ceb90fd0b1ab42d0a6ca39fcf55065c7');

try {
    $result = $client->getDensity(33.7794, -84.398, 'tue', 15);
    print_r($result);
} catch (Services_SimpleGeo_Exception $e) {
    echo "ERROR: " . $e->getMessage() . " (#" . $e->getCode() . ")\n";
}

?>

The third ($day) and fourth ($hour) arguments are both optional. If you do not specify a day it will use the current day according to PHP's date('D') so make sure your timezones and such are set up accordingly. Another thing to note is that you must specify an hour if you want a specific hours and that hours are in local time. Below is an example of what the output would look like.

stdClass Object
(
    [geometry] => stdClass Object
        (
            [type] => Polygon
            [coordinates] => Array
                (
                    [0] => Array
                        (
                            [0] => 33.779296875
                            [1] => -84.3984375
                        )

                    [1] => Array
                        (
                            [0] => 33.7802734375
                            [1] => -84.3984375
                        )

                    [2] => Array
                        (
                            [0] => 33.7802734375
                            [1] => -84.3974609375
                        )

                    [3] => Array
                        (
                            [0] => 33.779296875
                            [1] => -84.3974609375
                        )

                    [4] => Array
                        (
                            [0] => 33.779296875
                            [1] => -84.3984375
                        )

                )

        )

    [type] => Feature
    [properties] => stdClass Object
        (
            [hour] => 15
            [trending_rank] => -1
            [local_rank] => 5
            [city_rank] => 10
            [worldwide_rank] => 5
            [dayname] => tue
        )

)

You'll see that at 3PM local time Georgia Tech's population density on a Tuesday is trending downward, which makes sense as that's generally the end of a school day. Additionally, you see the full five points that enclose a four point box. Notice that the first point and last point in the list are the same.

About

A PEAR package for SimpleGeo's API

http://simplegeo.com


Languages

Language:PHP 100.0%