Tuesday

How to: Dealer Locator extension for Magento


In this article I will try to explain how to build a “Dealer Locator” extension for Magento. This article is all about extension concepts, structure and planing and not the code itself.
First we will start with scoping the feature. What functionality should it have? In our example, let’s imagine a client that wants a dealer locator to have “Search by nearest dealer based on ZIP/Postcode”, “Search by State”, “Search by dealer company name”. Search results should be displayed primarily on Google Map, plus it would be nice to have a table listing for those disliking the map visual style.
If we extend this a bit further, we soon come to realisation that “dealers” themselves should be a customers in our Magento system. Justification for this lies in a fact that dealers should be able to do a regular purchases on our Magento store like any other customer just with special prices applied to products. Since customer in Magento has to have a customer group assigned to it, and products special pricing can be applied for each customer group. Thus all we need to do in order to “convert” dealers to customers is to create a customer group “Dealer” or even use “Wholesale” for these types of customers. Then on each product set a special price for that customer group, that is if the dealers will have special prices.
Now, since dealers are just customers in our system then customer address(es) can be used for dealer locations. And this realization is the basis of our “Dealer locator” extension. Now that we have the general picture set, lets make a wireframe of the “Dealer locator” functionality.

Once the wireframe is approved by the client we can start with the preparations for development. Now, if we look back at the original requirement: “Search by nearest dealer based on ZIP/Postcode”, “Search by State”, “Search by dealer company name” we can easily see that “Search by nearest dealer based on ZIP/Postcode” part will be the most challenging one. “Search by State”, “Search by dealer company name” are straightforward as this is the direct property of each customer address. “Search by nearest dealer based on ZIP/Postcode” is something we will need to use certain calculations that involve geocode data.
General approach we will take towards “Search by nearest dealer based on ZIP/Postcode”:
- We will create an install script, for example app/code/community/Inchoo/Dealerlocator/sql/inchoo_dealerlocator_setup/install-1.0.0.0.php that adds two additional attributes to the customer address entity: inchoo_geo_latitude, inchoo_geo_longitude
- We will observe “customer_address_save_before” event and trigger a Inchoo_Dealerlocator_Model_Observer->injectLatLongIntoAddress() method once the event is fired. Note, injectLatLongIntoAddress is freely taken/chosen method name
- Within injectLatLongIntoAddress() we will execute a code that “contacts” the Google Maps API maps.google.com/maps/geo sending it the customer address information (namely street, postcode, city, country) then parses the response into the inchoo_geo_latitude, inchoo_geo_longitude properties of an address. Since we are observing customer_address_save_before event this data is then simply saved to customer address
- Once we have geo latitude and longitude data saved on each address we can now query the address collection for this information
- We still lack one important ingredient in this “Search by nearest dealer based on ZIP/Postcode” request, thats the “nearest”. We need to come up with a way to calculate the distance between two geo coordinates. There is a nice MySQL code snippet for this http://www.codecodex.com/wiki/Calculate_Distance_Between_Two_Points_on_a_Globe#MySQL. All we need to do is to transform to Magento collection :)
- Finally, the same way we did a geo mapping in the “customer_address_save_before” event for each address save, we will do a live geo mapping of the provided “referencing ZIP/Postcode”, the one that is provided on the frontend input text field. This way we have two geo location coordinates between which we calculate the distance: the referencing ZIP/Postcode geo coordinates vs. specific customer address geo coordinates
- Now we should have all pre-requirements for building the “Search by nearest dealer based on ZIP/Postcode”, we have latitude and longitude, we have distance.
I’m gonna be kind enough to provide you with my example of code for injectLatLongIntoAddress():


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$coordinates = array();$saveCoordinatesToAddress = true;$lineAddress = $address->getStreet1(). ', '.$address->getPostcode().' '.$address->getCity().', '.$address->getCountry();$client = new Zend_Http_Client();$client->setUri('http://'.self::GOOGLE_MAPS_HOST.'/maps/geo');$client->setMethod(Zend_Http_Client::GET);$client->setParameterGet('output', 'json');$client->setParameterGet('key', $this->getGoogleMapsApiKey());$client->setParameterGet('q', $lineAddress);$response = $client->request();if ($response->isSuccessful() && $response->getStatus() == 200) {    $_response = json_decode($response->getBody());    $_coordinates = @$_response->Placemark[0]->Point->coordinates;    if (is_array($_coordinates) && count($_coordinates) >= 2) {        $coordinates = array_slice($_coordinates, 0, 2);        if ($saveCoordinatesToAddress) {            try {                $address->setInchooGeoLongitude($coordinates[0]);                $address->setInchooGeoLatitude($coordinates[1]);                $address->save();            } catch (Exception $e) {                Mage::logException($e);            }        }    }}
Please note the $saveCoordinatesToAddress variable. If we pack the code above into a separate method we can reuse it for live geo mapping of the provided “referencing ZIP/Postcode” as well.
And here is the Magento customer collection object with included addresses and “distance” value calculation:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function getNearbyDealersLocations($radius, $centerLat, $centerLng)
{
    $customerGroup = Mage::getModel('customer/group');
    $customerGroup->load('Dealer', 'customer_group_code');
    if (!$customerGroup->getId()) {
        throw new Exception($this->__('Unable to load the customer group.'));
    }
    $collection = Mage::getResourceModel('customer/customer_collection')
                        ->addNameToSelect()
                        ->addAttributeToFilter('group_id', array('eq'=>$customerGroup->getId()))
                        ->joinAttribute('billing_company', 'customer_address/company', 'default_billing', null, 'left')
                        ->joinAttribute('inchoo_geo_latitude', 'customer_address/inchoo_geo_latitude', 'default_billing', null, 'left')
                        ->joinAttribute('inchoo_geo_longitude', 'customer_address/inchoo_geo_longitude', 'default_billing', null, 'left')
                        ->addAttributeToFilter('inchoo_geo_latitude', array('notnull'=>true))
                        ->addAttributeToFilter('inchoo_geo_longitude', array('notnull'=>true))
                        ->addExpressionAttributeToSelect('distance', sprintf("(3959 * acos(cos(radians('%s')) * cos(radians(at_inchoo_geo_latitude.value)) * cos(radians(at_inchoo_geo_longitude.value) - radians('%s')) + sin(radians('%s')) * sin( radians(at_inchoo_geo_latitude.value))))",   $centerLat, $centerLng, $centerLat, $radius), array('entity_id'));
    if ($radius !== 0) {
        $collection->getSelect()->having('distance < ?', $radius);
    }
    $collection->getSelect()->order('distance ' . Varien_Db_Select::SQL_ASC);
    //echo (string)$collection->getSelect(); exit;
    return $collection;
}


With the above in place you all that remains is to output the search results into the table or into the Google Map. There are plenty of example codes out there that show how to embed google maps with marker so I’m not going to cover this here. As mentioned at the beginning, the purpose of article is not to give a code ready solution but to give you an overview of the process that latter can then be given to developer so he can develop the feature.

courtsy: Branko Ajzele

No comments:

Django URLs

In Django, the urls.py file is where you define the URL patterns for your web application. There are several ways to write the urls.py fil...