Placing POis evenly relative to user's current location
Instead of placing POIs at a fixed physical location, POIs can be generated relative to user's current location. This means POIs' locations are dynamic and changed based on user's location. This is a good use case when POI content is not relevant to the physical world but something that the user can interact with no matter where s/he is. For instance, the "Battle Los Angeles" layer where you will find some alien armies around you all the time!! (scary, isn't it ? :))
In this tutorial, we will showcase how to place POIs relative to user's current location. What we want to achieve is:
- POIs are placed evenly around the user's location which is considered as the center of a circle. In the screenshot below, we put 4 POIs, but this number can be flexible.
- The distance between the POIs and the user location depends on the search range. So, POIs are placed at the edge of the circle considering the search range as the radius.
In this tutorial, we will explain all the changes(in BOLD) based on the sample code and database settings from the third tutorial . Please check previous tutorials, if you are not familiar with the code.
Calculate POIs location
The key in this tutorial is to find each POI's location(lat and lon). In order to do so, we need to know the following items:
- What is user's current location ?
It refers to user's latitude and longitude in signed decimal degrees. They can be retrieved from the getPOI request.
- Which POIs should be placed around the user ?
Can be retrieved from the POI database. Once POIs are retrieved, the number of POIs can be calculated.
- What is the bearing of each POI to user's location ?
Bearing is the direction of a fixed point, or the path of a moving object, from a point of observation. Bearings are angles measured in degrees (°) from the north line in a clockwise direction (between 0° and 360°).
- What is the distance (in km) between the POI and the user's location ?
In our case, it is dependent on the search range, the "radius" parameter which is passed through the getPOI request. In our case, we will put the POIs 50m shorter than the search range.
- What is earth's radius in km ?
It is 6371km.
We will use the following Formulas to calculate the POI latitude (lat2) and longitude(lon2):
Given: user's latitude (lat1), user's longitude (lon1), distance(in km) between the POI and the user (d), earth's radius in km (R), the bearing (in degree) of the POI from the user (θ).
Formula:
lat2 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(θ))
lon2 = lon1 + atan2(sin(θ)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2))
NOTE: The input parameters (lat1, lon1, θ) should be converted from values in degrees to values in radians. On the other hand, the output lat2 andlon2 should be converted to values in signed decimal degrees. We will not explain how these formulas are calculated, for more information, please visit http://www.movable-type.co.uk/scripts/latlong.html. Of course, this is not the only option out there, you can optimize/simplify the formula based on your needs.
Based on the formula provided above, we have created a function called getPoiLoc() which calculates the lat and lon of each POI. The input parameters are:
- user's latitude in signed decimal degrees ($lat)
- user's longitude in signed decimal degrees ($lon)
- the bearing of the POI to the user in degrees ($brng)
- the distance between the POI and the user in km ($dist).
function getPoiLoc ( $lat, $lon, $brng, $dist ) { // Convert $lat, $lon and $brng into radian values. $lat = DegtoRad ($lat); $lon = DegtoRad ($lon); $brng = DegtoRad ($brng); // The earth's radius in km. $R = 6371; // Calculate the latitude value (in radian) of the POI. $poi_lat = asin(sin($lat) * cos($dist/$R) + cos($lat) * sin($dist/$R) * cos($brng)); // Calculate the longitude value (in radian) of the POI. $poi_lon = $lon + atan2(sin($brng) * sin($dist/$R) * cos($lat) , cos($dist/$R) - sin($lat) * sin($poi_lat)); // Convert POI lat and lon from radians to signed decimal degrees. $poiloc['lat'] = RadtoDeg($poi_lat); $poiloc['lon'] = RadtoDeg($poi_lon); // return an array which contains the POI lat and lon in degrees. return $poiloc; }//getPoiLoc
As you can see that we also created two functions (DegtoRad() and RadtoDeg()) to convert values from degrees to radians and vice verse. These can be found in the complete sample code provided below.
Add a new field (relativePoi) to the database
To determine which POIs should be returned, we added a new enum parameter "relativePoi" to "POI" table. It has two values "yes" or "no". By default, it is "yes" and it means this POI is relative to the user and should be returned in the query.
You can use the following sql statement to add this new column:
ALTER TABLE `POI` ADD relativePoi ENUM('yes','no') NOT NULL DEFAULT 'yes'
Now the POI table looks like:
Retrieve relative POIs and store calculated POI locations in the response array
Now we need to retrieve POIs that should be displayed and gather the right input for calculating the POI location. The changes are made in function getHotspots().
- In the following mysql query, we retrieve POIs where relativePoi is 'yes' and poiType is 'geo'.
// Put received POIs into an associative array. The returned values are assigned to $response["hotspots"]. // // Arguments: // db ; The handler of the database. // value ; An array which contains all the needed parameters retrieved from GetPOI request. // // Returns: // array ; An array of received POIs. // function getHotspots( $db, $value ) { // Define an empty $hotspots array. $hotspots = array(); /* Create the SQL query to retrieve POIs with relativePoi = 'yes'. The first 50 POIs are selected and returned. */ // Use PDO::prepare() to prepare SQL statement. // $sql is returned as a PDO statement object. $sql = $db->prepare( ' SELECT id, imageURL, title, description, footnote, iconID, objectID, transformID FROM POI WHERE poiType = "geo" AND relativePoi = "yes" LIMIT 0, 50 ' ); // Use PDO::execute() to execute the prepared statement $sql. $sql->execute(); // Use fetchAll to return an array containing all of the remaining rows in // the result set. // Use PDO::FETCH_ASSOC to fetch $sql query results and return each row as an // array indexed by column name. $rawPois = $sql->fetchAll(PDO::FETCH_ASSOC);
- Once POIs are retrieved, we can prepare the input parameters for function getPoiLoc() and assign POI location to the POI array
/* Process the $pois result */ // if $rawPois array is not empty if ($rawPois) { // Iterator for the response array. $i = 0; // Count the number of returned POIs from the query. $num = count($rawPois); // Calculate the bearing difference between POIs. Divide 360 degrees evenly // by the number of POIs. $diff = (float)360 / $num; // Calculate the distance in km between POIs and the user's current // location using function getDistance(); The POIs will be placed at a // distance 50m shorter than the search range. $distance = getDistance($value['radius']); // The earth's radius, 6371 km $R = 6371; // Put each POI information into $hotspots array. foreach ( $rawPois as $rawPoi ) { $poi = array(); $poi['id'] = $rawPoi['id']; $poi['imageURL'] = $rawPoi['imageURL']; // Calculate each POI's position relative to the user. The first POI // always has the same lat as user's lat. $brng = $diff * $i; // Calculate POI's lat and lon in signed demical degrees. $poiLoc = getPoiLoc ($value['lat'], $value['lon'], $brng, $distance); // Get anchor object information $poi['anchor']['geolocation']['lat'] = changetoFloat($poiLoc['lat']); $poi['anchor']['geolocation']['lon'] = changetoFloat($poiLoc['lon']); // get text object information $poi['text']['title'] = $rawPoi['title']; $poi['text']['description'] = $rawPoi['description']; $poi['text']['footnote'] = $rawPoi['footnote']; //User function getPOiActions() to return an array of actions associated //with the current POI $poi['actions'] = getPoiActions($db, $rawPoi); // Get object object information if iconID is not null if(count($rawPoi['iconID']) != 0) $poi['icon'] = getIcon($db , $rawPoi['iconID']); // Get object object information if objectID is not null if(count($rawPoi['objectID']) != 0) $poi['object'] = getObject($db, $rawPoi['objectID']); // Get transform object information if transformID is not null if(count($rawPoi['transformID']) != 0) $poi['transform'] = getTransform($db, $rawPoi['transformID']); // Put the poi into the $hotspots array. $hotspots[$i] = $poi; $i++; }//foreach }//if return $hotspots; }//getHotspots
As you can see that we also created a new function called getDistance() to calculate the distance between the POI and the user. It is defined below:
// Calculate the distance between the POI and the user. It is 50m shorter than the search range (radius). // // Arguments: // float radius ; The search range in meters. // // Returns: // float ; The distance between the POI and the user in km. function getDistance ($radius) { // If $radius exists and it is not NULL. if (isset($radius)){ If ($radius <= 50) return (float)$radius / 1000; }else { // if $radius does not exist or the value is null. Return $radius as 1500m. $radius = 1500; } return (float)($radius - 50) / 1000; }//getDistance
Play with it
Ok, we have mentioned above all the changes that you need to make. The rest should remain the same as the third tutorial. You can find the attached sample php code to play with. Once you understand how this example works, you can alter it to meet your need. Before wrapping up the tutorial, lets see how it looks like when I place 10 POIs relative to user location evenly.