This is the final part of our series How to Add Postcode-Based Proximity Search With Open Data. In this part of our series we bring together the pieces and build an application that demonstrates how they work together.
So far in this series I have shown how to geocode a dataset, adding coordinates to describe the position of each record. You learned how to geocode a postcode supplied by the user, the maths to calculate the distance between two points, and how to incorporate that into a SQL query to order the records in a dataset by proximity to a given position.
We have now covered all the elements you need to build a proximity search. It’s time to bring together the pieces and build an (arguably) useful application that demonstrates how they work together -- and finds the closest pub!
The below PHP script brings all this together, connecting to MySQL. The script:
- Invites the user to enter a postcode.
- Connects to the API and passes the postcode entered to the API.
- Checks that the postcode is valid and can be geocoded.
- Uses the coordinates returned by the API to run a proximity search SQL query using MySQL.
- Prints the results.
<?php
// Define constants to hold the DB credentials.
define('DB_HOSTNAME', '');
define('DB_USER', '');
define('DB_PASSWORD', '');
define('DB_DATABASE', '');
// Set this to true if you are using a separate open_postcode_geo table in your SQL.
define("USE_OPEN_POSTCODE_GEO_TABLE", false);
?><html><head> <title>Pub Finder</title></head><body><h1>Pub Finder</h1><!-- The below links are not required! --><a href="https://www.getthedata.com/pub_finder.php">Live Demo</a> | <a href="https://www.getthedata.com/open-pubs">Source data</a> | <a href="https://www.getthedata.com/pub_finder.php.txt">Source code</a><hr><p>Enter a postcode to find the nearest pubs</p><form method="GET"> <input type="text" name="user_input" value="<? print htmlspecialchars(stripslashes($_GET['user_input'])) ?>"> <input type="submit" name="action" value="Search"></form><?php
// Run this block if the user has hit the "Search" button.
if(isset($_GET['action']) and $_GET['action'] == 'Search'){ // Check the user has entered something. if(!isset($_GET['user_input']) or empty($_GET['user_input'])){ print '<p style="color: red">Please enter a postcode</p>'; } else { // This is the Open Postcode Geo API endpoint. $endpoint = 'http://api.getthedata.com/postcode/'; // $_GET['user_input'] should be URL encoded. $url = $endpoint . urlencode($_GET['user_input']); // This fetches the API response and loads it into $response // as a string of JSON. $response = file_get_contents($url) or die('Cannot fetch ' . $url); // Decode the JSON into an associative array. $arr = json_decode($response, true) or die('Cannot decode ' . $response); // Ideally you would add some validation here to ensure you // have the result you are expecting from the API. // If match=no_match the postcode is not valid. if(isset($arr['status']) and $arr['status'] == 'no_match'){ print '<p style="color: red">"' . htmlspecialchars(stripslashes($_GET['user_input'])) . '" is not a valid postcode</p>'; } // If match_type!=unit_postcode then the user has entered a partial postcode. elseif(isset($arr['match_type']) and $arr['match_type'] != 'unit_postcode'){ print '<p style="color: red">Please enter the full postcode</p>'; } // If positional_quality_indicator!=9 then the postcode is non-geographic, i.e. it cannot be geocoded. elseif(isset($arr['data']['positional_quality_indicator']) and $arr['data']['positional_quality_indicator'] == 9){ print '<p style="color: red">' . htmlspecialchars(stripslashes($_GET['user_input'])) . ' cannot be geocoded</p>'; } // We should now have an easting and northing and so can fetch // the results from the open_pubs table. else { print '<hr>'; print '<h2>Results</h2>'; $link = mysqli_connect(DB_HOSTNAME, DB_USER, DB_PASSWORD, DB_DATABASE); if(USE_OPEN_POSTCODE_GEO_TABLE){ $sql = 'select open_pubs.name, open_pubs.address, open_pubs.postcode, round(sqrt(pow(abs(? - open_postcode_geo.easting),2) + pow(abs(? - open_postcode_geo.northing),2))) as distance from open_pubs join open_postcode_geo on open_pubs.postcode = open_postcode_geo.postcode where open_postcode_geo.easting is not null and open_postcode_geo.northing is not null and open_postcode_geo.easting between ? - 10000 and ? + 10000 and open_postcode_geo.northing between ? - 10000 and ? + 10000 order by distance limit 10'; $stmt = mysqli_prepare($link, $sql); mysqli_stmt_bind_param($stmt, 'iiiiii', $arr['data']['easting'], $arr['data']['northing'], $arr['data']['easting'], $arr['data']['easting'], $arr['data']['northing'], $arr['data']['northing']); } else { $sql = 'select name, address, postcode, round(sqrt(pow(abs(? - easting),2) + pow(abs(? - northing),2))) as distance from open_pubs where easting is not null and northing is not null order by distance limit 10'; $stmt = mysqli_prepare($link, $sql); mysqli_stmt_bind_param($stmt, 'ii', $arr['data']['easting'], $arr['data']['northing']); } mysqli_stmt_bind_result($stmt, $name, $address, $postcode, $distance); mysqli_stmt_execute($stmt) or die(mysqli_error($link)); mysqli_stmt_store_result($stmt); if(mysqli_stmt_num_rows($stmt) >= 1){ while(mysqli_stmt_fetch($stmt)){ print '<p><b>' . htmlspecialchars($name) . '</b> (' .$distance . 'm)<br>'; print htmlspecialchars($address) . ', ' . htmlspecialchars($postcode) . '</p>'; } // Print the required attribution statements. print '<hr>'; foreach($arr['copyright'] as $element) print $element . '<br>'; } else { print 'No results found for postcode ' . $arr['data']['postcode']; } mysqli_stmt_close($stmt); } }
}
?></body></html>
You can try a live demo of this script here:
https://www.getthedata.com/pub_finder.php
Happy pub-finding!
Attribution
Open Postcode Geo is derived from open data which require a number of attribution statements as a condition of use.
The API includes these in the response, in the copyright array:
Contains OS data (c) Crown copyright and database right 2016
Contains Royal Mail data (c) Royal Mail copyright and database right 2016
Contains National Statistics data (c) Crown copyright and database right 2016