Factual is a geographical database of places, services, points of interest and landmarks that is made available for free through their Application Programming Interface (API)
This post explains how to use a PHP library to access this database, the characteristics of the service, and the details on the content that can be retrieved from the Factual API.
Obtaining access keys
Requests to the Factual API must be authenticated with OAuth, and thus the first step in the implementation is to register and obtain a pair of OAuth access keys.
To do this, go to the Factual website, and click on GET STARTED > GET API KEY
The sign-up form that is displayed must be filled and submitted.
Once registered, follow the steps indicated, until the authorization keys are presented in the screen:
In this screenshot, we can see:
- The access level is free (unverified). This access level allows a maximum 100 daily queries. It can be upgraded to free (verified), with a limit of 10,000 daily queries. The upgrade request form asks for credit card details , even if the service is still free.
- In the request for API keys, access to the service can be restricted to a set of IPs and domains, if desired, to prevent unauthorized use of the service.
- Also in the request for API keys, the categories of interest need to be specified.
- Finally, at the bottom of the screen there are links for the download of API libraries for commonly used languages, including PHP.
Downloading the library and verifying the installation
The PHP library download link points to the Factual php driver page at github.com. The package installation file factual-php-driver-master.zip (202KB) can be downloaded from this page.
The package is uncompressed into a directory that contains all the scripts in the library. There is one “test.php” script among them, to check that the requirements of the library are met by the system. The script is run with the previously obtained OAuth keys as arguments:
$ php test.php FACTUAL-KEY FACTUAL-SECRET Testing Factual ======================== PHP verison v5+ Pass No classname conflicts Pass Parse INI File Function Pass SPL is loaded Pass curl is loaded Pass Creating Query object Pass Setting query parameter Pass API Connection Pass Limit/Filter/Sort Pass Unicode Filter Fail Punctuation Pass Quotes Test Pass 'In' Filter Pass Multi Filter Pass Geo Search Pass Multi Country Search Pass Response URL Pass Request Table Name Pass Response Headers Pass Response Code Pass Total Row Count Pass Submit Test Fail You do not have access to the requested resource. Reverse Geocoder Pass Creating Query object Pass Resolve Endpoint Pass Crosswalk Endpoint Pass Schema Endpoint Pass Diffs Test Pass Not Authorized , but that's expected' Checking Argentina Pass Checking Australia Pass Checking Austria Pass Checking Belgium Pass Checking Brazil Pass Checking Canada Pass Checking Chile Pass Checking China Pass Checking Columbia Pass Checking Croatia (Hrvatska) Pass Checking Czech Republic Pass Checking Denmark Pass Checking Egypt Pass Checking Finland Pass Checking France Pass Checking Germany Pass Checking Greece Pass Checking Hong Kong Pass Checking Hungary Pass Checking India Pass Checking Indonesia Pass Checking Ireland Pass Checking Israel Pass Checking Italy Pass Checking Japan Pass Checking Luxembourg Pass Checking Malaysia Pass Checking Mexico Pass Checking Netherlands Pass Checking New Zealand Pass Checking Norway Pass Checking Peru Pass Checking Philippines Pass Checking Poland Pass Checking Portugal Pass Checking Puerto Rico Pass Checking Russia Pass Checking Singapore Pass Checking South Africa Pass Checking South Korea Pass Checking Spain Pass Checking Sweden Pass Checking Switzerland Pass Checking Taiwan Pass Checking Thailand Pass Checking Turkey Pass Checking United Kingdom Pass Checking United States Pass Checking Venezuela Pass Checking Vietnam Pass ========================
Simple query to the Factual database
Let’s write a small script to issue a simple query to the Factual DB. The script begins with the loading of the main “Factual.php” library. Next, an instance of the “Factual” class is created with the OAuth credentials:
require_once('./factual-php-driver-master/Factual.php'); $factual = new Factual("FACTUAL-KEY","FACTUAL-SECRET");
Then, an instance of class “FactualQuery” is created. This object will be used to specify the query criteria to be applied:
$query = new FactualQuery; $query->at(new FactualPoint($latitude,$longitude)); $query->sortAsc("\$distance");
In this example, the query will ask for places near a given geographical point specified by its ($latitude,$longitude) coordinates. Additionally, the result will be sorted by distance to that point.
Finally, the API is accessed with a call to the fetch() method of the $factual object, with the entity type requested (“places”) and the query criteria in the $query object passed as arguments.
$res = $factual->fetch("places", $query); $numresults = $res->getTotalRowCount(); print_r $res->getData();
If the values of $latitude, $longitude are set to (40.759139, -73.985179) (the coordinates of Times Square in New York, according to Google Maps), an array of twenty entries is returned and printed to stdout (only the first entry is shown here):
Array (  => Array ( [factual_id] => 90109b13-65fb-4194-8944-0aa0f01bdef3 [latitude] => 40.75917 [longitude] => -73.985211 [$distance] => 4.3756595 [name] => Blue Fin [address] => 1567 Broadway [category_ids] => Array (  => 364  => 366  => 348 ) [category_labels] => Array (  => Array (  => Social  => Food and Dining  => Restaurants  => Seafood )  => Array ( ... )  => Array ( ... ) ) [country] => us [region] => NY [locality] => New York [neighborhood] => Array (  => Midtown  => Midtown West  => Theatre District ) [postcode] => 10036 [email] => firstname.lastname@example.org [tel] => (212) 918-1400 [tel_normalized] => 2129181400 [website] => http://www.bluefinnyc.com [hours] => Array ( [monday] => Array (  => Array (  => 7:00  => 23:00 ) ) [tuesday] => Array ( ... ) ... ) [hours_display] => Mon 7:00 AM-11:00 PM; Tue-Thu 7:00 AM-11:30 PM; Fri-Sat 7:00 AM-11:59 PM; Sun 7:00 AM-11:00 PM ) ...  => Array ( ... ) )
As can be seen in the example above, the information retrieved from each entry includes:
- A unique entry identifier (“factual_id”)
- latitude and longitude where the place is located, and distance from the point specified in the search
- categories where the entry belongs, in a hierarchical structure. In the example result above, category id 364 is Social > Food and Dining > Restaurants > Seafood.
- Additional information can optionally be retrieved for each entry. In the example, the name of the restaurant, address, phone number, email and opening hours are included.
Search criteria and parameters
The simple search example analyzed so far, requested places located around a given point identified by its (lat,lon) coordinates, sorted by distance. These criteria are defined by calling the at() method in the FactualQuery class.
There are other methods in the FactualQuery class that can be used to use other search criteria, such as:
- public function within($geo) – Restricts the search to places located inside the area specified in the $geo argument (a circle or rectangle)
- public function search($term) – Requests a “full text search”
- public function limit($limit) – Establish a limit on the number of records retrieved. The default value if not specified is 20, and the maximum that can be requested is 50.
- public function offset($offset) – Establish the first record to be retrieved from the full result set (used for pagination)
- public function includeRowCount() – Include in the response the total number of records matching the search criteria (with some performance penalty)
- public function select($fields) – Specify with fields are to be returned for each record. The default is to return all fields in the “places” table (See “Places table Schema” below)
- public function field($field) – Specify search criteria on the values of a given field (See Full Text Search examle below)
- public function sortAsc($field), public function sortDesc($field) – Sorts in ascending or descending order of the values of the field passed as argument.
Full Text Search
$query = new FactualQuery; $query->search("Sushi Santa Monica"); //searches the text in all fields of the places table $query->field("country")->equal("US"); //limit results to places in the US $res = $factual->fetch("places", $query);
Search up to ten results inside a 5Km radius
$query = new FactualQuery; $query->within(new FactualCircle(34.06018, -118.41835, 5000)); // 5 Km radius circle $query->limit(10); //Limita a 10 resultados $query->sortAsc("\$distance"); //sort ascending by distance to the given coordinates $res = $factual->fetch("places", $query);
Places table schema
To be able to specify search criteria base on the values of fields in the places table, we must know the names of the fields. A query for the table schema can be issued to the API, as follows:
$res = $factual->schema("places"); print_r($res->getColumnSchemas());
This returns an array with the definition of all 287 fields in the places table:
Array ( [name] => FactualColumnSchema Object ( [name] => name [description] => Business/POI name [faceted] => [sortable] => 1 [label] => Name [datatype] => String [searchable] => 1 ) [address] => FactualColumnSchema Object ( [name] => address [description] => Address number and street name [faceted] => [sortable] => 1 [label] => Address [datatype] => String [searchable] => 1 ) ...
Each of the records in the Factual database is assigned one or several categories: Restaurants, Museums, Pharmacies,…
Searching for records in a given category or categories is probably one of the most commonly used search criteria. The category filter is applied by restricting the results to those with specific values in the “category_ids” field, as in the following example:
Here, $category is the numeric identifier of the category of interest ( 80: Pharmacies, 311: Museums,…)
Categories in Factual are hierarchically structured: Categoría 80 (Pharmacies) belongs to category 62 (Health), together with categories 69 (Dentists), 74 (Hospitals), etc…
The complete list of the more than 400 categories can be found at:
The list can be downloaded in JSON format from that same page