Overview
This plugin allows you to have your map dynamically load points of interest as the user pans around the map allowing you to minimize the amount of data you have to fetch from the server and minimize the number of markers on the map speeding up performance by only dealing with points that are within the map’s visible area.
The Plugin
The plugin monitors the user’s panning and zooming and triggers a load of the specified store or leaves you to implement your own loading functions by hooking into its ‘mapload’ event.
Configuration Options
| units | the units that you would like to deal in. This can be set to ‘miles‘ or ‘km‘. Defaults to ‘miles‘ |
| bufferType | how you would like the map’s buffer to operate. Possible values are ‘ratio‘ or ‘fixed‘.This property allows you to include a ‘buffer’ distance to be added to the visible area’s radius allowing extra points to be loaded that won’t be initially visible. This means that when the user pans there are points present immediately before the full load is complete, making the user experience much more seamless.fixed: this means that the value of the ‘buffer‘ config is always used as the buffer distance no matter what zoom level the map is on.ratio: by using this setting the value of the ‘buffer‘ config option (set between 0 and 1) is multiplied by the visible area’s radius to get a proportional distance outside the visible area to include in the load. For example if you are at a zoom level which means a 1 mile radius is visible to the user and you have a ratio buffer value of 0.5 then you will load points within a circle of 1.5 miles. This is useful when you consider a huge change in zoom. If we had a fixed buffer value of 0.5 miles in the previous situation then that would be acceptable, however if the user zoomed out to a visible radius of 200 miles then we would load very few extra points to smooth the load when the user pans. If we had a ratio buffer of 0.25 (25%) then at a zoom level with 1 mile radius we would load 1.25 miles and at a zoom level with 200 mile radius we would load an extra 50 miles giving us much more points outside the visible area to make the pan-load smoother. |
| buffer | the value that will be used when calculating the Buffer Radius.If bufferType is set to ‘ratio‘ then the value should be a decimal between 0-1 (equivalent to a percentage) or if bufferType is set to ‘fixed‘ then this can be any number in the units defined in the units config. |
| store | if defined the store will be automatically loaded when needed by the plugin – it effectively hooks up the ‘mapload’ event and loads the store with the correct parameters.If left null, it will be ignored and you must define your loading implementation. |
| disabled | when set to true, the plugin will be disabled and no dynamic loading will happen.Can be modified using the enable(), disable() or setDisabled(value) methods |
| loadInterval | this config can be used to fire loads while a user pans rather than only at the end of the pan movement.The number of milliseconds between loads while the user is panning.Set to 0 for no intermediate loads to take place. |
Events
One event is added to the plugin’s parent Ext.Map object. The ‘mapload‘ event is fired when the user finishes a pan movement (including pauses in movement, not just when the touch is removed) or, if the loadInterval is set, at the defined intervals. The event passes 5 parameters to its handlers:
| centre | the map’s current centre. An object in the form { lat: xx, lng: yy } |
| bounds | the bounds of the map’s current visible area. An object in the form:
{
northeast: {
lat: xx,
lng: yy
},
southwest: {
lat: xx,
lng: yy
}
}
|
| boundingRadius | the radius of the visible map. I.e. the distance from the centre point to the corners of the visible region. Measured in the units provided in the units config. |
| bufferRadius | the radius that has been calculated based on the bufferType and buffer config options. This doesn’t get added to the boundingRadius and is applied on the server. |
| zoom | the current zoom level of the map. |
An Example
The plugin itself is very simple with the complexity coming in the implementation of the load and post-load logic (e.g. adding markers etc). I will describe an example using an AJAX request rather than defining a store.
Defining the plugin
{
xtype: 'map',
plugins: [new Ext.ux.touch.MapLoader({
units: 'miles',
bufferType: 'ratio',
buffer: 0.25, // calculate 25% of the visible radius,
loadInterval: 300 // do a load every 300 ms during panning
})],
centered: true,
mapOptions: {
center: new google.maps.LatLng(55.857809, -4.242511), // centre over Glasgow
zoom: 15
}
}
Elsewhere we hook into the Map’s new ‘mapload‘ event and fire an AJAX call to the server passing up all the event’s parameters. The server code (which we’ll look at in a second) responds with a list of lat/lng positions and then our JS adds these points to our map.
// code found within the mapload's event handler
Ext.Ajax.request({
url: 'server/search.php',
method: 'get',
params: {
centre: Ext.encode(centre),
bounds: Ext.encode(bounds),
boundingRadius: boundingRadius,
bufferRadius: bufferRadius,
zoom: zoom
},
success: function(response){
var jsonResponse = Ext.decode(response.responseText);
if (jsonResponse.result) {
for (var i = 0; i < jsonResponse.result.length; i++) {
var resultPos = jsonResponse.result[i];
if (!this.markerExists(resultPos.lat, resultPos.lng)) { // custom method (see full example code) to ignore points we've already loaded
// cache the newest point so we don't add it in the future
this.markerCache.add({
lat: resultPos.lat,
lng: resultPos.lng
});
var pos = new google.maps.LatLng(resultPos.lat, resultPos.lng);
var marker = new google.maps.Marker({
map: this.map,
position: pos
});
}
}
}
},
scope: this
});
The serverside code simply parses the parameters being passed up and inputs them into a SQL version of the Haversine Formula. It then returns the results as JSON to be consumed by our JS code.
// Get parameters from URL
$centre = json_decode(stripslashes($_GET["centre"]), true);
$bounds = json_decode(stripslashes($_GET["bounds"]), true);
$boundingRadius = $_GET["boundingRadius"];
$bufferRadius = $_GET["bufferRadius"];
$zoom = $_GET["zoom"];
// Opens a connection to a mySQL server
// Set the active mySQL database
// Search the rows in the markers table
$query = sprintf("SELECT lat, lng, ( 3959 * acos( cos( radians('%s') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('%s') ) + sin( radians('%s') ) * sin( radians( lat ) ) ) ) AS distance FROM markers2 HAVING distance < '%s' ORDER BY distance",
mysql_real_escape_string($centre['lat']),
mysql_real_escape_string($centre['lng']),
mysql_real_escape_string($centre['lat']),
mysql_real_escape_string($boundingRadius) + mysql_real_escape_string($bufferRadius)); // add boundingRadius and bufferRadius
$result = mysql_query($query);
if (!$result) {
die("Invalid query: " . mysql_error());
}
$outputArray = array();
while($array = mysql_fetch_array($result)){
$outputArray[] = $array;
}
echo json_encode(array("success" => true, "count" => count($outputArray), "result" => $outputArray));
To use the ‘store‘ config simply move the ‘success’ function defined above into the store’s load event handler and it will have the same effect.
And that’s us, you should see your points loading as you pan the map!
There are a couple points you must consider when using the plugin and when implementing your surounding code, these are:
- Duplicate points – it is highly possible (and likely!) that the same point will be included in multiple loads so you must take this into consideration when processing the loaded points to stop markers being added twice.
- Redundant markers – markers that are off screen are still there so you may want to implement some clean-up code to destroy these markers when not needed to speed up performance.
If you have any problems with using the plugin, have questions about how it works or have suggestions about ways we can make it better then please leave us a comment or drop us an email!