Search

Ok, here’s a holiday challenge for everyone: I am putting together a site the matches service providers with service clients, and I need to be able to match them based on geographic proximity. The distance range will be associated to the provider (the range the provider is capable of offering services to). I intend to store both client and provider location as lat/long.

How would I go about getting a datasource that would provide an output from the providers section that a particular client is in range of? I imagine the DS would need to take the client’s lat/long as the filter. I have found this explanation of the haversine formula directly in SQL to calculate range, but that is about as far as I’ve gotten.

I’m thinking I would need to create a new field whose value would be calculated at query time, then filter by that, but I’m really not too familiar with that part of Symphony.

Any help would be awesome!

Theres likely many ways to go about this, but here’s one option.

Create a Location field extension. Provide the field with the following options in the section editor:

  • Label (standard)
  • Placement (standard)
  • Comparison Field (options: none, provider, etc)

The Location field will create a database table containing:

  • id (standard)
  • entry_id (standard)
  • long_lat
  • closest_entry_id

If Comparison Field is none

The system will deem this as a normal long/lat field and simply save the long/lat values in the database. In your case, this will be the Provider section.

The value stored in the database would be the long/lat with the closest_entry_id being null.

If Comparison Field is Provider

The field will compare the Client section’s Location field against the values stored in Provider and determine which of the entries is closest to the Client. The field then saves the long/lat of the Client (for posterity) and saves the closest calculated Provider’s entry_id in the closest_entry_id db field.

Andrew, I did something similar on the Heineken site. It’ll default to your Geo-IP location, but you can enter a postcode (try WD3 1HH for beer near me!).

This was created before the time of the superb Map Location field, and the GeoIP data source extension. So each entry has two Number fields: Latitude and Longitude. On the client-side, the Google Maps API (geocoder) was used to convert a postcode into a lat/long, which is passed as URL Parameters to an AJAX/XML page with a custom DS attached:

/get-stores-by-latlong/{lat}/{long}/

I customised the DS’s grab() function as:

function grab(&$param_pool){
    $result = NULL;     

    $radius = 20;

    $start_lat = $this->_env['env']['url']['lat'];
    $start_lng = $this->_env['env']['url']['lng'];

    $range_lat = $radius / ((6076 / 5280) * 60);
    $range_lng = $radius / (((cos($start_lat * 3.141592653589 / 180) * 6076.) / 5280.) * 60);

    $min_lat = $start_lat - $range_lat;
    $max_lat = $start_lat + $range_lat;
    $min_lng = $start_lng - $range_lng;
    $max_lng = $start_lng + $range_lng;

    // lat = 102, lng = 103
    $this->dsParamFILTERS = array(
            '102' => 'mysql: value <= ' . $max_lat . ' AND value >= ' . $min_lat,
            '103' => 'mysql: value >= ' . $min_lng . ' AND value <= ' . $max_lng
    );

    include(TOOLKIT . '/data-sources/datasource.section.php');

    if($this->_force_empty_result) $result = $this->emptyXMLSet();
    return $result;
}

Which will find locations in a 20 mile (or is that kilometer?) radius.

I wrote this 18 months ago, mind, so you might need to modify this slightly if Symphony has changed. However you could make this even cleaner by adding this functionality directly into the Map Location field. Since this field stores a lat/long pair in the database, you could write a DS filter along the lines of:

within:20,{lat},{long}

And then convert this into an SQL query. Maybe.

I’m certain this has been discussed before (as to what the best filter syntax would be: “within”, “nearer than” etc) but I couldn’t find the thread.

I’ve been chatting to brendo about this on Github and when I get some time I will try and modify his fork of the Map Location Field to support location-based filtering. Something like:

within 20 km of 10.545, -103.1

within 10 miles of London

within 2km of 1 West Street, Burleigh Heads

within 500 miles of England or Scotland or Wales

Thanks guys for all the input.

Modifying the location field for this filtering sounds like it would be quite useful. So, defining a filter is essentially field behavior in the context of a datasource?

In my case, I will need to additionally record provider radius, and use that in the filter, something like

within range of {$ds-client-location}

Where range is an optional radius added to the Location field (in this case the Provider’s). Does that make sense?

So, defining a filter is essentially field behavior in the context of a datasource?

Yep.

Are you saying that the radius needs to be dynamic? i.e. for one provider it could be 10 but for another it could be 50? If you go with my custom DS method then you could set the value of the $radius variable from another source.

Are you saying that the radius needs to be dynamic?

Yes, the providers will have different radii, but the goal is for the DS to output the providers whose ranges include a given client location. So it looks like your custom DS will be the most direct route, so to speak.

I will be working on this during the holidays, so I’ll keep you posted on progress. Thanks again!

Enjoy your mulled wine, Christmas ‘puddings’, and whatever else you Dickensian types have this time of the year.

I have been fiddling around with the Map Location Field as well.

So far I have only come around to rewrite the backend JS to resolve to adresses when dragging the pin (and it’s completely jQuery-based now) and some of the returned XML:

http://github.com/phoque/maplocationfield

I was planning on enabling queries like “within 10 miles of London” but got stuck somewhere. :-)

So please, serve yourself on the JS-part.

Cheers phoque. Brendo has also done some JS work in removing the API key requirement (API v3) so I’ll try and merge all of these together into a Map Location Field v3.

@nickdunn: Might be worth having a squiz at the Geokit ruby gem if you’re looking into more advanced filtering. It’s the library I’ve been using on Decaf Sucks to do all the location-based stuff.

Thanks Max. I didn’t end up using any parts from Geokit, but it was good inspiration. Have posted my take on the Map Location Field here which provides “store locator” functionality. Any comments specific to this field, please post over at the above thread.

This is more a feature request for this extension. In the resulting XML would it be possible to add the full address? It would be a great addition for outputting the information on individual location pages.

Potentially. However it’s not always accurate. My postcode where I live for example is shared between about thirty residences, meaning the address wouldn’t be accurate.

Well, as an example, what could be done is that instead of typing out the address as a single string you could manually enter in the address in a form (similar to the Address field) and in the datasource it would give you that information with the geotag info.

Geocoding is already provided by the Address field?

Yes, but not a physical address.

When you go to get the location you have to type in the full street address of the location. Google finds the location and passes you back the longitude and latitude of said location.

However, you don’t have the written-out address anymore. What I would like to see is either a way for you to be able to pull that information from Google or that instead of writing the address out in one input field (which you do now) you write it out into individual fields which are then concatenated and sent to Google for the longitude and latitude. However, instead of simply giving you the Long/Lat for the location the XML would also have the full address so you could have it written down.

This would be useful if you wanted to use the same information not only to find the closest store but also have the address so that you could mail the store something, put it in your address book, whatever.

I was just trying out your extension. Nicely done! :-)

But I see you didn’t implement reverse geocoding. Why is that? Especially because my try did. :-)

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details