UPDATED 2010-22-05: I enhanced the first part of the algorithm. It does not longer use a delta computed empirically by looking at maps. Now it dynamically creates a box around the destination using a user given offset.
Starting from Dycapo Server v0.3.0 we introduced a new searching algorithm for riders. This algorithm is still in an early development status and will improve in the next releases. Unfortunately, we could not rely on vectorial calculus on real maps because of budget restrictions. Therefore, every calculation is done using air-to-air distances. Tests show that works pretty well.
The algorithm uses geolocation techniques for real-time matching between a driver and riders. In this article I am going to explain the basic functionalities of the algorithm.
It is divided in more functions for having better readability and separation of concerns.
The algorithm is implemented in the matching.py module in the server package.
The following is the pseudocode with explanations:
The main part of the algorithm. It makes three function calls. Every sub-function has the scope to filter and exclude Trips, therefore refining the search
search_trip(source, destination):
trips_common_destination := get_trips_destination_near_location(destination)
trips_driver_farther_driver := exclude_trips_driver_closest_to_destination(trips_common_destination, rider)
trips_approaching_rider := exclude_trips_driver_not_approaching_rider(trips_driver_farther_driver, rider)
return trips
The first sub-function searches for Trips with a destination set around rider's destination.
Returns all the Trips with a destination near a given location.
We create a virtual box around the destination, using a given offset or a fixed offset of 150 meters when none is available. See Location.get_box_around() for more info.
We query the database for all active Trips with a destination that is inside
this box, and return the QuerySet
get_trips_destination_near_location(location):
box := get box from location
lat_max := max(box top left latitude, box top right latitude)
lat_min := min(box bottom right latitude, box bottom left latitude)
lon_max := max(box top right longitude, box bottom right longitude)
lon_min := min(box top left longitude, box bottom left longitude)
trips_destination_near_location := query database for trips with constraints(
active = True,
destination with latitude in range (lat_min, lat_max),
destination with longitude in range (lon_min, lon_max)
)
return trips_destination_near_location
The second sub-function filters the retrieven Trips in which the driver is nearer to the destination than the rider
Given a list of Trips, this function removes all the Trips for which the driver is closer to the destination than the rider
exclude_trips_driver_closest_to_destination(trips, rider):
for trip in trips:
driver_distance_from_destination := compute distance from driver to destination
rider_distance_from_destination := compute distance from rider to destination
if driver_distance_from_destination < rider_distance_from_destination:
exclude trip from trips
return trips
The third sub-function is the most complex. It excludes Trips in which the driver is not getting nearer the rider. That is, even if the driver is farer from the destination than the rider, he/she could take a path that is bringing him/her away from the rider. This sub-function is also divided in other three sub-routines, defined later.
Given a list of Trips and a rider, it retrieves the proximity factor (defined later) and removes the Trip from the list
if the factor is less than a decided tolerance
exclude_trips_driver_not_approaching_rider(trips, rider):
for trip in trips:
if get_proximity_factor(trip.author, rider.position) < -2:
exclude trip from trips
return trips
The following are the three sub-routines of the last function. They make some very simple calculations to give a quality rate to the retrieven results.
Given a person and a location, it determines if the person is approaching it or getting away from it, by retrieving some recent locations of the person and computing their distance from the location. The set of ordered distances is then given to location_proximity_factor that retrives the factor
get_proximity_factor(person, position):
recent_locations := get Person's recent locations
recent_locations_distance_from_position = [ ]
for location in recent_locations:
recent_locations_distance_from_position.append(distance from location to position)
proximity_factor := location_proximity_factor(recent_locations_distance_from_position)
return proximity_factor
Given a list of distances, it computes the approaching factor which is a natural number in (-inf , +inf)
- If factor > 0, the numbers in list tend to decrease
- If factor == 0, the numbers in list tend to stay around the same value
- If factor < 0, the numbers in list tend to increase.
location_proximity_factor(distances):
factor := 0
for i in range(0, len(distances)):
factor := factor + location_distance_factor(distances[i], distances[i + 1])
return factor
Given two distances:
- returns 1 if the first distance is greater than the second one.
- returns -1 if the first distance is less than the second one.
- returns 0 if they are equal.
location_distance_factor(distance1, distance2):
if distance1 > distance2:
return 1
if distance1 < distance2:
return -1
return 0