Laravel 5 Geocoder Tutorial

Download: Laravel 5 Geocoder Tutorial Files

Introduction

This tutorial shows you how to create a GIS powered web application using Laravel 5 and GeocoderLaravel library. Our web application will use AngularJS to create a cool interface and LeafletJS to display the maps.

In a nutshell, we will create a hotels directory for Tanzania. The hotels information will be stored in a database. We will use GeocoderLaravel library to geocode the hotel name to get the latitude and longitude. By the time that you are done with this tutorial, you will have the following application

Applictaion demo gif

Topics to be covered

In this tutorial, we will cover the following topics.

  • Application Pre-requisites
  • Application architectural design
  • Installing and configuring GeocoderLaravel
  • Laravel 5 RESTful API backend
  • Hotels search directory AngularJS front end
  • Displaying maps using LeafletJS

Application Pre-requisites

  1. Basic knowledge of Laravel
  2. You should have Composer, PHP, MySQL and a Web server on your computer
  3. A modern web browser
  4. An IDE or Text editor.

Application architectural design

Our application will have a RESTful backend and use AngularJS for the frontend. It’s not mandatory to use a RESTful backend or AngularJS frontend

Why a RESTful backend?

The advantage of a RESTful backend is that you can build a mobile, desktop or web application that consumes the API, without having to rewrite anything on the backend. Today’s applications are expected to behaviour this way.

Why AngularJS frontend

AngularJS makes it very easy and speeds up developing rich interface applications RIA. You don’t have to be a JavaScript ninja to develop cool interfaces. AngularJS encapsulates everything for you. You will mostly be working with HTML like tags when working with AngularJS.

Installing and configuring GeocoderLaravel ##

Let’s start by creating a new Laravel project. This tutorial assumes you are using XAMPP on windows and XAMPP is installed to drive C. The knowledge still applies to other platforms with minimal modifications

Installation

  1. Open the command prompt / terminal
  2. Run the following command to browse to the web root
cd "C:\xampp\htdocs"

Now that we are in the web root, let’s create a Laravel project using composer

Run the following composer command

composer create-project laravel/laravel tzhotels

After the project is created successfully, edit the generated composer.json file to include GeocoderLaravel.

  1. open /composer.json
  2. Add the following line to required key
"toin0u/geocoder-laravel": "@stable"

Your complete file should now appear similar to the following

{
    "name": "laravel/laravel",
    "description": "The Laravel Framework.",
    "keywords": ["framework", "laravel"],
    "license": "MIT",
    "type": "project",
    "require": {
        "php": ">=5.5.9",
        "laravel/framework": "5.1.*",
		"toin0u/geocoder-laravel": "@stable"
    },
    "require-dev": {
        "fzaninotto/faker": "~1.4",
        "mockery/mockery": "0.9.*",
        "phpunit/phpunit": "~4.0",
        "phpspec/phpspec": "~2.1"
    },
    "autoload": {
        "classmap": [
            "database"
        ],
        "psr-4": {
            "App\\": "app/"
        }
    },
    "autoload-dev": {
        "classmap": [
            "tests/TestCase.php"
        ]
    },
    "scripts": {
        "post-install-cmd": [
            "php artisan clear-compiled",
            "php artisan optimize"
        ],
        "pre-update-cmd": [
            "php artisan clear-compiled"
        ],
        "post-update-cmd": [
            "php artisan optimize"
        ],
        "post-root-package-install": [
            "php -r \"copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "php artisan key:generate"
        ]
    },
    "config": {
        "preferred-install": "dist"
    }
}

Let’s now install GeocoderLaravel.

Run the following composer command

composer update

Configuration

We need to make a few configuration settings to /config/app.php file. We need to register the service provider and add a class alias name

  1. open /config/app.php
  2. Locate the providers array and add the following line at the end
Toin0u\Geocoder\GeocoderServiceProvider::class,

Locate the aliases array and add the following line at the end of the array

'Geocoder' => Toin0u\Geocoder\Facade\Geocoder::class,

Save the changes Just to make sure everything is working ok, check the following URL in your browser

http://localhost/tzhotels/public/welcome

You should be able to see the welcome page. Use the comments section below to ask if you run into any problems.

Laravel 5 RESTful API backend

Our simple RESTful API will be retrieving data from the database. Let’s start with configuring the database settings before we proceed. Run the following command in MySQL to create a database.

CREATE DATABSE tzhotels;

Now that we have created our database, let’s edit .env file to set the correct database parameters Open .env file in your project root directory Locate the following lines

DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

update the database, username and password to match the settings on your instance of MySQL. Below is an example

DB_HOST=localhost
DB_DATABASE=tzhotels
DB_USERNAME=root
DB_PASSWORD=melody

Migrations

Migrations are a great way of programmatically creating the database. We will create a hotels table in our database.

Run the following command to install the migration table

php artisan migrate:install

Run the following command to create the migration file for the hotels table

php artisan make:migration create_hotels_table
  1. Open the newly created migration file in /database/migrations/xxxxxxxxxcreatehotelstable. Note: xxxxxx_xxx stands for the migration timestamp.
  2. Modify the code to the following
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateHotelsTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('hotels', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('description',500);
            $table->string('image');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::drop('hotels');
    }
}

Let’s now run the migration file to create the database table

php artisan migrate

Seeding

Seeding is used to add dummy records to a database table. Run the following command to create a database seed

php artisan make:seeder HotelsTableSeeder

Open /database/seeds/HotelsTableSeeder.php and modify the code to the following

<?php

use Illuminate\Database\Seeder;

class HotelsTableSeeder extends Seeder {

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run() {
        DB::table('hotels')->insert([
            'name' => 'Zacale Hotel',
            'description' => 'Zacale hotel, Morogoro Road, Ndugumbi, Sisi Kwa Sisi, Makumbusho, Dar es Salaam, Coastal, 78570.',
            'image' => 'zacale',
        ]);

        DB::table('hotels')->insert([
            'name' => 'Southern Sun Hotel Dar es Salaam',
            'description' => 'Southern Sun Hotel Dar es Salaam, Hamburg Avenue, Mnazi Mmoja, Posta Mpya, Makumbusho, Dar es Salaam, Coastal, 3918, Tanzania.',
            'image' => 'southern_sun',
        ]);

        DB::table('hotels')->insert([
            'name' => 'Hotel Agip',
            'description' => 'Hotel Agip, Pamba Rd., Mnazi Mmoja, Posta Mpya, Dar es Salaam, Coastal, 3918, Tanzania.',
            'image' => 'agip',
        ]);
    }
}

Run the following command to add the records to the database

php artisan db:seed --class=HotelsTableSeeder

API Routes

This example only needs to read from the API but for the sake of completeness, we will also add methods for adding new records, updating and deleting data.

Let’s start with the routes

  1. open /app/Http/routes.php files
  2. modify it to the following code
<?php

/*
  |--------------------------------------------------------------------------
  | Application Routes
  |--------------------------------------------------------------------------
  |
  | Here is where you can register all of the routes for an application.
  | It's a breeze. Simply tell Laravel the URIs it should respond to
  | and give it the controller to call when that URI is requested.
  |
 */

Route::get('/', function () {
    return view('index');
});

Route::get('/api/v1/hotels/{id?}', 'Hotels@index');
Route::post('/api/v1/hotels', 'Hotels@store');
Route::post('/api/v1/hotels/{id}', 'Hotels@update');
Route::delete('/api/v1/hotels/{id}', 'Hotels@destroy');

Route::get('/api/v1/coordinates/{name}', function($name) {
    try {
        $geocode = Geocoder::geocode("$name, Tanzania")->toArray();
        return Response::json($geocode);
    } catch (\Exception $e) {
        echo $e->getMessage();
    }
});

HERE,

  • The above code defines URLs for our API using respective HTTP verbs for the required action. Our routes are calling methods in a controller Hotels.
  • Route::get('/api/v1/coordinates/{name}', function($name){…} defines the API URL that we will use for geocoding. Geocoding is the process of getting the latitude and longitude for a supplied string parameter.
  • $geocode = Geocoder::geocode("$name, Tanzania")->toArray(); calls the geocode method of Geocoder class. geocode is responsible for geocoding the hotel name that we pass in. Tanzania has been added to narrow the search to only Tanzania. feel free to change it to match your country.

API Controller

Let’s now create the controller for our API Run the following artisan command

php artisan make:controller Hotels
  1. Open the newly created controller in /app/Http/Controllers/Hotels.php
  2. Modify the code to the following
<?php

namespace App\Http\Controllers;

use App\Hotel;
use Geocoder\Geocoder;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;

class Hotels extends Controller {

    /**
     * Display a listing of the resource.
     *
     * @return Response
     */
    public function index($id = null) {
        if ($id == null) {
            return Hotel::orderBy('id', 'asc')->get();
        } else {
            return $this->show($id);
        }
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request) {
        $hotel = new Hotel;

        $hotel->name = $request->input('name');
        $hotel->description = $request->input('description');
        $hotel->type = $request->input('type');
        $hotel->image = $request->input('image');
        $hotel->save();

        return 'Hotel record successfully created with id ' . $hotel->id;
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id) {
        return Hotel::find($id);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function update(Request $request, $id) {
        $hotel = Hotel::find($id);

        $hotel->name = $request->input('name');
        $hotel->description = $request->input('description');
        $hotel->type = $request->input('type');
        $hotel->image = $request->input('image');
        $hotel->save();

        return "Sucess updating hotel #" . $hotel->id;
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return Response
     */
    public function destroy($id) {
        $hotel = Hotel::find($id);
        $hotel->delete();

        return "Hotel record successfully deleted #" . $id;
    }

}

HERE,

  • use App\Hotel; imports the model namespace
  • public function index($id = null){…} is used to either retrieve all records or retrieve a record based on the supplied id.
  • public function store(Request $request){…} is used to add a new hotel record to the database
  • public function show($id){…} retrieves a single hotel record based on id
  • public function update(Request $request, $id){…} updates an existing hotel record
  • public function destroy($id){…} deletes an existing hotel record

That was it for our controller.

API Model

Let’s now create the model that our controller interacts with

Run the following artisan command

php artisan make:model Hotel
  1. Open the newly created model in /app/Hotel.php
  2. Modify the code to the following
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Hotel extends Model
{
     protected $fillable = array('id', 'name', 'description','image');
}

Testing our backend RESTful API

Load the following URL in your web browser

http://localhost/tzhotels/public/api/v1/coordinates/Zacale

You will get the following results

Use composer here

If you do not get the above results or get an error, use the comments section below to ask for help.

Load the following URL to view a list of hotels

http://localhost/tzhotels/public/api/v1/hotels

You will get the following results

Use composer here

If you do not get the above results or get an error, use the comments section below to ask for help.

Hotels search directory AngularJS frontend

Let’s now create the frontend for our application. The frontend will have the following pages

Use composer here

Note: The hotel pops up with cool CSS3 and AngularJS Animations when you hover over the mouse

The following page shows the details page

Use composer here

If you are completely new to Laravel 5 and AngularJS, then I recommend you read the tutorial Laravel 5 AngularJS Tutorial. The AngularJS code in this tutorial is fully explained in the linked tutorial. You can still use the comments section to ask questions.

Our AngularJS application will have the following structure

Use composer here

The libraries, images and css files for the application have been included in the tutorial file. I recommend you download them. Let’s start with the code in /public/app/app.js

var app = angular.module('tzPoints', [
    'ngRoute',
    'ngAnimate'
])
        .constant('API_URL', 'http://localhost/tzhotels/public/api/v1/');

app.config(['$routeProvider', 'API_URL', function($routeProvider) {
        $routeProvider.
                when('/list', {
                    templateUrl: 'app/views/list.html',
                    controller: 'ListController'
                }).
                when('/details/:hotelId', {
                    templateUrl: 'app/views/details.html',
                    controller: 'DetailsController'
                }).
                otherwise({
                    redirectTo: '/list'
                });
    }]);

HERE,

  • var app = angular.module('tzPoints', ['ngRoute','ngAnimate']) defines an AngularJS module tzPoints and injects ngRoute and ngAnimate as dependencies.
  • .constant('API_URL', 'http://localhost/tzhotels/public/api/v1/'); defines a constant variable for the API URL.
  • app.config(['$routeProvider', 'API_URL', function($routeProvider){…} defines two controllers for our application and a default route that should be executed if the requested route is not defined.

That was it for /public/app/app.js

The controller is the one that links our views with the data from the API. Let’s now look at the code for /public/app/controllers/controllers.js

app.controller('ListController', function($scope, $http, API_URL) {
    $http.get(API_URL + "hotels")
            .success(function(response) {
                $scope.hotels = response;
                $scope.orderBy = 'name';
            });
});

app.controller('DetailsController', function($scope, $http, $routeParams, API_URL) {
    $http.get(API_URL + "hotels/" + $routeParams.hotelId).success(function(data) {
        $scope.hotel = data;

        getCoordinates($scope.hotel, $http, $scope, API_URL);
    });   
});

function getCoordinates(hotel, $http, $scope, API_URL) {
    $http.get(API_URL + "coordinates/" + encodeURI(hotel.name))
            .success(function(response) {
                $scope.latitude = response.latitude;
                $scope.longitude = response.longitude;
                
                initMap($scope.latitude,$scope.longitude,hotel);
            });
            
            
            
}

function initMap(latitude, longitude,hotel) {
    var map = L.map('map').setView([latitude, longitude], 13);

    L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6IjZjNmRjNzk3ZmE2MTcwOTEwMGY0MzU3YjUzOWFmNWZhIn0.Y8bhBaUMqFiPrDRW9hieoQ', {
        maxZoom: 18,
        attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
                '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
                'Imagery © <a href="http://mapbox.com">Mapbox</a>',
        id: 'mapbox.streets'
    }).addTo(map);


    L.marker([latitude, longitude]).addTo(map)
            .bindPopup(hotel.name).openPopup();

    var popup = L.popup();

    function onMapClick(e) {
        popup
                .setLatLng(e.latlng)
                .setContent("You clicked the map at " + e.latlng.toString())
                .openOn(map);
    }

    map.on('click', onMapClick);
}

HERE,

  • app.controller('ListController', function($scope, $http, APIURL){…} defines and attaches a controller ListController to the app variable. The app variable is defined in app.js. This controller is responsible for retrieving a list of all hotels. $scope, $http, APIURL are supplied to the anonymous function as parameters. $scope will allow us to share data between the views and our controller. $http is used to call the API and API_URL is the constant variable with the API URL.
  • app.controller('DetailsController', function($scope, $http, $routeParams, API_URL){...} defines and attaches a controller DetailsController to the app variable. We have passed in an extra parameter $routeParams. $routeParams is used to retrieve URL variables in AngularJS. We want to get the hotel id and pass it as a parameter to our REST API.
  • function getCoordinates(hotel, $http, $scope, API_URL) this function is used to geocode the hotel name and return the latitude and longitude among other details. The hotel name is retrieved from the hotel parameter that we are passing in when calling this function.

Displaying maps using LeafletJS

LeafletJS is a JavaScript library for displaying maps on the web. You can read more about LeaftletJS on the official website Let me replicate the code for the function initMap() and explain it here

function initMap(latitude, longitude,hotel) {
    var map = L.map('map').setView([latitude, longitude], 13);

    L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6IjZjNmRjNzk3ZmE2MTcwOTEwMGY0MzU3YjUzOWFmNWZhIn0.Y8bhBaUMqFiPrDRW9hieoQ', {
        maxZoom: 18,
        attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
                '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
                'Imagery © <a href="http://mapbox.com">Mapbox</a>',
        id: 'mapbox.streets'
    }).addTo(map);


    L.marker([latitude, longitude]).addTo(map)
            .bindPopup(hotel.name).openPopup();

    var popup = L.popup();

    function onMapClick(e) {
        popup
                .setLatLng(e.latlng)
                .setContent("You clicked the map at " + e.latlng.toString())
                .openOn(map);
    }

    map.on('click', onMapClick);
}

HERE,

  • var map = L.map('map').setView([latitude, longitude], 13); creates a map objected and passes in the latitude and longitude
  • L.tileLayer(…).addTo(map); creates a map tile layer using mapbox API, defines the zoom value and map attributes
  • L.marker([latitude, longitude]).addTo(map).bindPopup(hotel.name).openPopup(); creates a map pop with the hotel name.
  • function onMapClick(e){…} defines the map click event. This event shows the latitude and longitude of a place on the map that the user clicked.

Application Images, JavaScript, CSS and HTML files ##S

Download the attached tutorial file and you will get all of the above files.

Testing our application

Load the following URL in your web browser.

http://localhost/tzhotels/public/

You will be redirected to

http://localhost/tzhotels/public/#/list

Try to search for the letter a You will get a list of hotels Click on any of the hotels You will get results similar to the one below

Summary

GeocoderLaravel makes it easy to work with geographical data within a Laravel application. LeafletJS is a powerful light weight JavaScript library that simplifies displaying maps.

What’s next?

If you found this tutorial useful, then share it on social media to support us. You can also give us feedback via the comments section. Happy coding and sharing.

Tutorial History

Tutorial version 1: Date Published 2015-09-03

Related Tutorials