How to track our pizza in realtime with Pusher and Google Maps

In this tutorial, we will cover how to build a realtime map to track pizza delivery using Pusher and Google Maps API.
I was really hungry the other night and ordered something old school style and called up this small canteen which has some delicious, fresh and mouth-watering pizza! Now they did not have a tracker app and I couldn’t wait to hog on the cheesy deliciousness so I decided to track it myself!
It wasn’t much of an effort though, thanks to the Pusher APIs the task was easy peasy! So I gave this link to my delivery hero (Anyone who delivers pizza is a hero) and he, having a better smart phone than I do, got on-board quickly and I could track my pizza! :D
Here’s a sneak peak of how our app will look once it’s done.
Step 0: Setting up our app
- Start off by creating an
index.html
with this code snippet. - Our humble
index.html
doesn’t do much except including ameta
tag in thehead
section so that our app looks crisp and works great in mobile browsers as well.
<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<title>Realtime Tracking with Pusher</title>
<meta charset="utf-8">
</head>
<body>
<h1>Food Express - Track your food delivery realtime!</h1>
</body>
</html>
- Since we’ll be using Google Maps, let’s add their JavaScript API and see the map in action!
- But before you can integrate Google Maps you need to get your key. Click here to get the key.
- Once you get the key, copy it, and include their JS file in the
index.html
. Also add the script tag forapp.js
which will contain our app’s code. - We’ll also add a CSS file
app.css
for making our app look nice! - This is how your
index.html
will look like:
<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<title>Realtime Tracking with Pusher</title>
<meta charset="utf-8">
<link href="app.css" rel="stylesheet"></link>
</head>
<body>
<div class="header">
<h1>Food Express - Track your food delivery realtime!</h1>
</div>
<div class="container">
<div id="map"></div>
</div>
<script src="https://maps.googleapis.com/maps/api/js?key=INSERT_YOUR_KEY_HERE"></script>
<script type="text/javascript" src="src/app.js"></script>
</body>
</html>
- Let’s add some code quickly to render the map in our
app.js
(function () {
// load the map
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: -34.397, lng: 150.644},
zoom: 8
});
}());
Step 1: Capture device’s location
- We’ll use Web’s Geolocation API to get the user’s location on start-up.
- With this code, we get the location and centre the map. See we’re already into delivering a personalized experience!
// get the location via Geolocation API
if ('geolocation' in navigator) {
var currentLocation = navigator.geolocation.getCurrentPosition(function (position) {
map.setCenter({
lat: position.coords.latitude,
lng: position.coords.longitude
});
});
}
Step 2: Capture username
- It’s important to capture a username to identify my Delivery Hero on the map.
- Let’s code a
div
which will take input and store it in-memory. - Add the following code in
index.html
<div id="name-box" class="name-box">
<h3>Enter your username</h3>
<input id="name" type="text" placeholder="e.g. Mike">
<button id="saveNameButton">Save</button>
</div>
- And some JavaScript to get the name.
var username;
// reference for DOM nodes
var saveNameButton = document.getElementById('saveNameButton');
var saveNameBox = document.getElementById('name-box');
var nameInput = document.getElementById('name');
var welcomeHeading = document.getElementById('welcome-message');
var deliveryHeroBox = document.getElementById('delivery-hero-box');
saveNameButton.addEventListener('click', saveName);
// all functions, event handlers
function saveName (e) {
var input = nameInput.value;
if (input && input.trim()) {
username = input;
// hide the name box
saveNameBox.classList.add('hidden');
// set the name
welcomeHeading.innerHTML = 'Hi! <strong>' + username +
(mode === 'user'
? '</strong>, type in your Delivery Hero\'s name to track your food.'
: '</strong>, type in the customer name to locate the address');
// show the delivery hero's div now
deliveryHeroBox.classList.remove('hidden');
}
return;
}
Step 3: Set up tracking logic and send events on location change for realtime map
- To track the location of our Delivery Hero (and pizza) we’ll use Pusher’s real time capabilities. We’ll trigger events whenever we change our location and also at the same time listen for the location change events of our Delivery Hero.
- Signup for Pusher, or Login if you already have an account.
- Once you login, create an app by giving an
app-name
and choose acluster
in the Create App screen. - Now that we’ve registered and created the app, add
Pusher's JavaScript library
in yourindex.html
<script src="https://js.pusher.com/4.0/pusher.min.js"></script>
- Connect to your app by calling the
Pusher
constructor with yourapp key
as shown in the below line.
var pusher = new Pusher('<INSERT_PUSHER_APP_KEY_HERE>', {
cluster: '<INSERT_PUSHER_CLUSTER_HERE>',
encrypted: true
});
- Next, I need to start triggering events when my location changes, so that my Delivery Hero knows that I am at my friend’s place now. (He said he was hungry too and I am a good guy :D ).
- While we’ll trigger events for our location change, we need to secure these events so that only intended recipients can track us. We’ll accomplish this by using Pusher’s Channel concept.
- Channels are a way to filter and secure events. In our app each user will be represented as a
channel
. We’ll be using Pusher’s Private Channels.
var myLocationChannel = pusher.subscribe('private-<USERNAME>');
- A
channel
will be named after the username chosen by the user, and with this the other party can subscribe and listen the location change events for a particular user. - To use private channels, you must be authenticated. Pusher makes writing an auth server very easy. I used their NodeJS template here.
- My server.js looks like this:
var express = require('express');
var bodyParser = require('body-parser');
var Pusher = require('pusher');
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// to serve our JavaScript, CSS and index.html
app.use(express.static('./'));
var pusher = new Pusher({
appId: 'INSERT_YOUR_APP_ID_HERE',
key: 'INSERT_YOUR_KEY_HERE',
secret: 'INSERT_YOUR_SECRET_HERE',
cluster: '<INSERT_PUSHER_CLUSTER_HERE>'
});
app.post('/pusher/auth', function(req, res) {
var socketId = req.body.socket_id;
var channel = req.body.channel_name;
var auth = pusher.authenticate(socketId, channel);
res.send(auth);
});
var port = process.env.PORT || 5000;
app.listen(port, () => console.log('Listening at http://localhost:5000'));
- To trigger events we’ll be using Pusher’s Client Events as clients can directly trigger these events from their devices and they need not necessarily go via a server first.
- You need to enable
Client Events
in yourSettings
tab on Pusher’s Dashboard. Client Events
should start withclient-
. (Note thatClient Events
have a number of restrictions that are important to know about while creating your awesome app. Read more about them here).- On startup we create a channel using the code below, and then send our client events to it every time we change location.
- We’ll also save the last location in an object (
myLastKnownLocation
) for later retrieval.
function createMyLocationChannel (name) {
var myLocationChannel = pusher.subscribe('private-' + name);
myLocationChannel.bind('pusher:subscription_succeeded', function() {
// safe to now trigger events
// use the watchPosition API to watch the changing location
// and trigger events with new coordinates
locationWatcher = navigator.geolocation.watchPosition(function(position) {
var location = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
triggerLocationChangeEvents(myLocationChannel, location);
});
// also start a setInterval to keep sending the loction every 5 secs
sendLocationInterval = setInterval(function () {
// not using `triggerLocationChangeEvents` to keep the pipes different
myLocationChannel.trigger('client-location', myLastKnownLocation)
}, 5000);
});
}
- To handle the case when the user isn’t moving, we add a
setInterval
to keep sending the last captured location. This means that my Delivery Hero can track my last location, if I dozed off!
sendLocationInterval = setInterval(function () {
// not using `triggerLocationChangeEvents` to keep the pipes different
myLocationChannel.trigger('client-location', myLastKnownLocation)
}, 5000);
// also update myLastKnownLocation everytime we trigger an event
function triggerLocationChangeEvents (channel, location) {
// update myLastLocation
myLastKnownLocation = location;
channel.trigger('client-location', location);
}
Step 4: Subscribe to Delivery Hero’s location channels
- First up, code a div in
index.html
to enter Delivery Hero’s username.
<div id="delivery-hero-box" class="name-box hidden">
<h3 id="welcome-message"></h3>
<h4 id="delivery-heroes-list"></h4>
<input id="deliveryHeroName" type="text" placeholder="e.g. Shelly">
<button id="addDeliveryHeroButton">Add</button>
</div>
Let’s make the button functional by adding an event listener on it:
deliveryHeroesAddButton.addEventListener('click', addDeliveryHero);
- So every time you add a username,
addDeliveryHero
function would get called.
function addDeliveryHero (e) {
var deliveryHeroName = deliveryHeroNameInput.value;
// if already present return
if (deliveryHeroesLocationMap[deliveryHeroName]) return;
if (deliveryHeroName) {
var deliveryHeroChannelName = 'private-' + deliveryHeroName;
var deliveryHeroChannel = pusher.subscribe(deliveryHeroChannelName);
deliveryHeroChannel.bind('client-location', function (nextLocation) {
// first save the location
// bail if location is same
var prevLocation = deliveryHeroesLocationMap[deliveryHeroName] || {};
deliveryHeroesLocationMap[deliveryHeroName] = nextLocation;
showDeliveryHeroOnMap(deliveryHeroName, false, true, prevLocation);
});
}
// add the name to the list
var deliveryHeroTrackButton = document.createElement('button');
deliveryHeroTrackButton.classList.add('small');
deliveryHeroTrackButton.innerHTML = deliveryHeroName;
deliveryHeroTrackButton.addEventListener('click', showDeliveryHeroOnMap.bind(null, deliveryHeroName, true, false, {}));
deliveryHeroesList.appendChild(deliveryHeroTrackButton);
}
- In the above code, we first
subscribe
to theprivate
Pusher channel of the Delivery Hero.
var deliveryHeroChannelName = 'private-' + deliveryHeroName;
var deliveryHeroChannel = pusher.subscribe(deliveryHeroChannelName);
- And listen to all the events triggered on that channel.
deliveryHeroChannel.bind('client-location', function (nextLocation) {
// first save the location
// bail if location is same
var prevLocation = deliveryHeroesLocationMap[deliveryHeroName] || {};
deliveryHeroesLocationMap[deliveryHeroName] = nextLocation;
showDeliveryHeroOnMap(deliveryHeroName, false, true, prevLocation);
});
- We keep the
event name
the same i.e.client-location
as every user has a distinct channel. - Read more about keeping the data private here.
- Each new event contains the latest location and we save that in an object to retrieve later.
- Also we take the help of another function to plot the location on a map,
showDeliveryHeroOnMap
.
function showDeliveryHeroOnMap (deliveryHeroName, center, addMarker, prevLocation) {
if (!deliveryHeroesLocationMap[deliveryHeroName]) return;
// first center the map
if (center) map.setCenter(deliveryHeroesLocationMap[deliveryHeroName]);
var nextLocation = deliveryHeroesLocationMap[deliveryHeroName];
// add a marker
if ((prevLocation.lat === nextLocation.lat) && (prevLocation.lng === nextLocation.lng)) {
return;
}
if (addMarker) {
var marker = deliveryHeroesMarkerMap[deliveryHeroName];
marker = marker || new google.maps.Marker({
map: map,
label: deliveryHeroName,
animation: google.maps.Animation.BOUNCE,
});
marker.setPosition(deliveryHeroesLocationMap[deliveryHeroName]);
deliveryHeroesMarkerMap[deliveryHeroName] = marker;
}
}
- The above function adds a marker at the new location on the map, and bails if the new location is same as previous location.
- If we’re already tracking a Delivery Hero, it updates the new location in the same marker.
- If the person is moving, you can see the marker moving on the map in realtime. ZOMG! I KNOW! More excited because as soon as I finish writing this I can eat my tracked pizza! :D
Step 5: Finding multiple Delivery Heroes, all at once
- Now that you’ve learned how to find a Delivery Hero in the above step, we can do it for multiple Heroes. Yay! I am a foodie and I know it.
- Notice that we are also adding a button (
deliveryHeroTrackButton
) for all the Heroes that we add via the textbox. - On clicking that button we’ll center the screen with the last known location of that Delivery Hero.
Next Steps
- Since we’re using the awesome concept of Pusher Channels, we can easily build on top of it to track anything: food parcel or an e-commerce delivery.
- Also, Google Maps API integration is a piece-of-cake.
PS: Pusher’s realtime capability FTW!
Running locally
- Clone the repository
git clone [email protected]:ankeetmaini/food-express.git
cd food-express
npm install
npm start
- Generate Pusher API keys and insert in
src/app.js
as well asserver.js
- Generate Google Maps API key and add in
index.html
- Open http://localhost:5000
Running the Demo
- To track your food delivery open https://food-express.herokuapp.com/
- Login with your username, any name for sake of simplicity.
- The Delivery Hero should also Login at this address https://food-express.herokuapp.com/?mode=delivery
- In a typical Food Delivery service, once you place an order, you receive some confirmation with the expected time of delivery and the delivery person’s contact information and name/username. Assuming you received an SMS/E-mail of the same
- Type in the username of the Delivery person to track your Food!
Show me the code
- Code is hosted on GitHub.
May 18, 2017
Ready to begin?
Start building your realtime experience today.
From in-app chat to realtime graphs and location tracking, you can rely on Pusher to scale to million of users and trillions of messages