Build a realtime map with Laravel

Introduction

Realtime maps have become very popular with more and more couriers, delivery and transportation services apps using them to show realtime tracking of your order or available vehicles around you. Today we will learn how to build a realtime map using Laravel and Pusher.

What you'll be using

  • Pusher
  • Google Maps
  • Laravel
  • Angular

Set up Pusher

To get started you need to sign up to a free Pusher account here. After you have created your account and are logged into the Pusher dashboard, click the Create new App button. Name your new app "Laravel-Map" and select a cluster. A cluster represents the physical location of the servers that handle requests from your app, therefore selecting a cluster closer to your location will mean faster response.

Next select the frontend and backend technologies - in this case, Angular and Laravel. (If you want, you can also give a short description of what app you're building.)

realtime-map-laravel-create-app

When you've done, click the "Create my app" button.

Next, navigate to the "App Keys" tab on the dashboard to see your application keys. Note your application keys because we will be using them later in this tutorial.

Creating the Laravel Project

Navigate into the directory where you would like your apps to be and install Laravel via composer by running the following command in your terminal.

composer create-project --prefer-dist laravel/laravel laravel-map

Add pusher-php-server to the required dependencies of your composer.json file so that it looks similar to this.

1"require": {
2        "php": ">=5.6.4",
3        "laravel/framework": "5.4.*",
4        "laravel/tinker": "~1.0",
5        "pusher/pusher-php-server": "^2.2"
6    },

The pusher-php-server is a PHP library which will help us broadcast events to Pusher which will then be listened to by our client side. To install it, run the following command

composer install

After installation completes, rename the .env.example file to .env and add the keys you got from Pusher to their respective places in the file. Also set the BROADCAST_DRIVER to pusher and add a PUSHER_APP_CLUSTER key with its value set to your Pusher apps cluster. It should look similar to the following:

1BROADCAST_DRIVER=pusher
2PUSHER_APP_ID=XXXXX
3PUSHER_APP_KEY=XXXXXXXXXXXXXXXXX
4PUSHER_APP_SECRET=XXXXXXXXXXXXXXXXXX
5PUSHER_APP_CLUSTER=XXXXXXXXXX

Next, open the config/broadcasting.php file and set the Pusher cluster option like this:

1<?php
2
3return [
4
5   ...
6
7    'connections' => [
8
9        'pusher' => [
10            'driver' => 'pusher',
11            'key' => env('PUSHER_APP_KEY'),
12            'secret' => env('PUSHER_APP_SECRET'),
13            'app_id' => env('PUSHER_APP_ID'),
14            'options' => [
15                //add this line
16                'cluster' => env('PUSHER_APP_CLUSTER'),
17            ],
18        ],
19
20   ...

Next, run the following command to generate a Laravel secret key.

php artisan key:generate 

We will be using a public channel to make this tutorial as simple as possible, so we won't be changing anything else.

Next, let us create a SendLocation event. To do this, run the following Laravel command:

php artisan make:event SendLocation

You should see a PHP file in your app/Events directory named SendLocation.php . The only thing we will be changing in the file is the channel-name. We will also implement the ShouldBroadcast interface and add a public variable to it. The complete code should look similar to the following:

1<?php
2
3namespace App\Events;
4
5use Illuminate\Broadcasting\Channel;
6use Illuminate\Queue\SerializesModels;
7use Illuminate\Foundation\Events\Dispatchable;
8use Illuminate\Broadcasting\InteractsWithSockets;
9use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
10
11class SendLocation implements ShouldBroadcast
12{
13    use Dispatchable, InteractsWithSockets, SerializesModels;
14
15    /**
16     * Create a new event instance.
17     *
18     * @return void
19     */
20    public $location;
21
22    public function __construct($location)
23    {
24        $this->location = $location;
25    }
26
27    /**
28     * Get the channels the event should broadcast on.
29     *
30     * @return Channel|array
31     */
32    public function broadcastOn()
33    {
34        return new Channel('location');
35    }
36}

Finally, create an endpoint where coordinates will be sent. When requests are made to the endpoint, the SendLocation event will be triggered and the coordinates will be sent to Pusher. We'll do that in our web.php file located in the routes folder. Add the code below the file:

1Route::post('/map', function (Request $request) {
2    $lat = $request->input('lat');
3    $long = $request->input('long');
4    $location = ["lat"=>$lat, "long"=>$long];
5    event(new SendLocation($location));
6    return response()->json(['status'=>'success', 'data'=>$location]);
7});

Set up a Google Map project

We will be using Google Maps to implement our realtime map. This guide will run you through registering a project in the Google API Console and activating the Google Maps JavaScript API. Remember to grab the API key that will be generated for you after registering.

Working with Angular

We'll be using Angular for our frontend. If you don't have angular installed, run the following command:

npm install -g @angular/cli

Now create an Angular app with the following command:

ng new angular-map

Next we will install dependencies to listen to events sent to Pusher by our server. Pusher has a JavaScript library for frontend technologies which we'll be using to listen for events from Pusher.

We'll also be installing Laravel Echo. To do this, navigate into the "angular-map" project and run the following command:

npm install --save laravel-echo pusher-js

Now that we have installed the dependencies, let's get to the code. In your index.html file, add Pusher and Google Maps scripts. Your index.html file should look similar to the code snippet below:

1<!doctype html>
2<html>
3<head>
4  <title>Simple Map</title>
5  <meta name="viewport" content="initial-scale=1.0">
6  <meta charset="utf-8">
7
8</head>
9<body style="margin: 0">
10
11<script src="https://js.pusher.com/3.0/pusher.min.js"></script>
12<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_GOOGLE_APP_KEY"></script>
13  <app-root>Loading...</app-root>
14
15</body>
16</html>

We need somewhere to render the map, so add the following line to your app.component.html file:

<div id="map"></div>

Finally, we move over to our app.component.ts file to add the code which will render Google Maps on our HTML page.

1import { Component, OnInit } from '@angular/core'
2import * as Echo from 'laravel-echo';
3
4declare var google:any;
5
6const PUSHER_API_KEY = 'YOUR_PUSHER_KEY';
7const PUSHER_CLUSTER = 'YOUR_PUSHER_CLUSTER';
8
9@Component({
10  selector: 'app-root',
11  templateUrl: './app.component.html',
12  styleUrls: ['./app.component.css']
13})
14
15export class AppComponent implements OnInit{
16
17  lat : number = 9.0820;
18  long : number = 8.6753;
19
20  ngOnInit() {
21
22    this.launchMap(this.lat, this.long);
23
24  }
25
26
27  launchMap(lat, lng){
28    var nigeria= {lat: lat, lng: lng};
29    this.map = new google.maps.Map(document.getElementById('map'), {
30      zoom: 8,
31      center: nigeria
32    });
33    this.marker = new google.maps.Marker({
34      map: this.map,
35      animation:"bounce",
36    });
37    this.lineCoordinates.push(new google.maps.LatLng(this.lat, this.long));
38  }
39}

Next we add the code which listens to the event and updates the map when the coordinates change.

1subscribe(){
2    var echo = new Echo({
3      broadcaster: 'pusher',
4      key: PUSHER_API_KEY,
5      cluster: PUSHER_CLUSTER
6    });
7    echo.channel('location')
8      .listen('SendLocation', (e)=>{
9         this.data = e.location;
10          this.updateMap(this.data);
11      });
12  }
13
14updateMap(data){
15    this.lat = parseFloat(data.lat);
16    this.long = parseFloat(data.long);
17
18    this.map.setCenter({lat:this.lat, lng:this.long, alt:0});
19    this.marker.setPosition({lat:this.lat, lng:this.long, alt:0});
20
21    this.lineCoordinates.push(new google.maps.LatLng(this.lat, this.long));
22
23    var lineCoordinatesPath = new google.maps.Polyline({
24      path: this.lineCoordinates,
25      geodesic: true,
26      map: this.map,
27      strokeColor: '#FF0000',
28      strokeOpacity: 1.0,
29      strokeWeight: 2
30    });

The complete code snippet for the app.component.ts file:

1import { Component, OnInit } from '@angular/core';
2import * as Echo from 'laravel-echo';
3import * as Pusher from 'pusher-js';
4
5declare let google:any;
6
7const PUSHER_API_KEY = 'YOUR_PUSHER_KEY';
8const PUSHER_CLUSTER = 'YOUR_PUSHER_CLUSTER';
9
10@Component({
11  selector: 'app-root',
12  templateUrl: './app.component.html',
13  styleUrls: ['./app.component.css']
14})
15
16export class AppComponent implements OnInit{
17  data : any;
18  map : any;
19  lat : number = 9.0820;
20  long : number = 8.6753;
21  marker : any;
22  lineCoordinates = [];
23
24  ngOnInit() {
25
26    this.subscribe();
27    this.launchMap(this.lat, this.long);
28
29  }
30
31
32  subscribe(){
33    let echo = new Echo({
34      broadcaster: 'pusher',
35      key: PUSHER_API_KEY,
36      cluster: PUSHER_CLUSTER
37    });
38    echo.channel('location')
39      .listen('SendLocation', (e)=>{
40         this.data = e.location;
41          this.updateMap(this.data);
42      });
43  }
44
45  launchMap(lat, lng){
46    let nigeria= {lat: lat, lng: lng};
47    this.map = new google.maps.Map(document.getElementById('map'), {
48      zoom: 14,
49      center: nigeria
50    });
51    this.marker = new google.maps.Marker({
52      map: this.map,
53      animation:"bounce",
54    });
55    this.lineCoordinates.push(new google.maps.LatLng(this.lat, this.long));
56  }
57
58  updateMap(data){
59    this.lat = parseFloat(data.lat);
60    this.long = parseFloat(data.long);
61
62    this.map.setCenter({lat:this.lat, lng:this.long, alt:0});
63    this.marker.setPosition({lat:this.lat, lng:this.long, alt:0});
64
65    this.lineCoordinates.push(new google.maps.LatLng(this.lat, this.long));
66
67    let lineCoordinatesPath = new google.maps.Polyline({
68      path: this.lineCoordinates,
69      geodesic: true,
70      map: this.map,
71      strokeColor: '#FF0000',
72      strokeOpacity: 1.0,
73      strokeWeight: 2
74    });
75  }
76}

Realtime map demo

Here is a gif showing the map being updated in realtime:

realtime-map-laravel-demo

In order to see the marker move on the map, you will need to send App\Events\SendLocation events to the location channel. The easiest way to do this is by using the event creator on the Pusher Debug Console. Here is a sample data format that can be used to trigger an update:

1{
2  "location": {
3    "lat": "9.084999999999999",
4    "long": "8.678299999999998"
5  }
6}

Here is an image of how the event would look on the Pusher event creator:

ealtime-map-laravel-send-event

Alternatively, location updates can also be triggered by sending web requests to the Laravel application. Here is an Angular function that can be used to send location update requests:

1sendLocation(lat: string, long: string) {
2  const serverUrl = 'http://localhost:8000';
3  const params = new URLSearchParams();
4  params.set("lat", lat);
5  params.set("long", long);
6
7  return this.http.post(serverUrl + '/map', params);
8}

Note that the above function is assuming that the Laravel application is accessible via 'http://localhost:8000', you can update the serverUrl to your own configuration.

Conclusion

We have successfully created a real time map which will be updated when new coordinates are sent to the endpoint on our server. You could use this to track the location of almost anything - but remember, if you are using this in production, you should consider privacy needs and build that functionality in.

You can find the complete project in these repositories angular app and laravel app.