Laravel: Find Addresses with Coordinates via Google Maps API

Tutorial last revisioned on August 10, 2022 with Laravel 9

Quite often we need to save addresses in the database, and we want them to be accurate. Probably, the best way to ensure it is to provide customers with input field where they choose the address from auto-completed values as they type. This article will show you how to achieve it in Laravel, let's build a "location picker" with Google Maps API.

First, this is what we're aiming for:

Our form to create companies will contain a few things:

  • Address input text field which is auto-completed via Google Maps API
  • Embedded Google Map which will place a marker, when user chooses the address from the list
  • Two hidden fields for latitude and longitude which will be automatically filled, when user chooses the address from the list

Step 1. Preparing database and model

In our migrations, we should have something like this:

Schema::create('companies', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('address_address')->nullable();
    $table->double('address_latitude')->nullable();
    $table->double('address_longitude')->nullable();
    $table->timestamps();
});

As you can see, there are THREE columns, not one. Well, one for the actual address string and two for the geographical coordinates.

We also make them fillable in model app/Models/Company.php:

class Company extends Model
{
    protected $fillable = [
        'name',
        'address_address',
        'address_latitude',
        'address_longitude',
    ];

Finally, storing these values will be very straightforward - here's how CompaniesController looks:

public function store(StoreCompanyRequest $request)
{
    abort_unless(\Gate::allows('company_create'), 403);
    $company = Company::create($request->all());
    return redirect()->route('admin.companies.index');
}

In other words, we're filling all the fields that form contains, and auto-filling address_latitude / address_longitude will be all in JavaScript "magic". Let's get to it.


Step 2. Preparing Google Maps API

This is probably the step that most tutorials will be skipping, because it's not about the code. It's about access to Google Maps API. To use it, you need to create an application in Google Developer Console and enable a bunch of APIs related to Maps.

Not only that, in June 2018 Google changed Maps API usage and pricing. Basically, you can't use Google API without enabling billing in Google Developer Console. In other words, you need to put your credit card there.

At this moment of writing (January 2019), there's $200 per month of free credit, but as soon as you reach it, the price is pretty heavy. Here's a graph from one of my client's project - we reached free credit limit on 13th of the month, and from there it only depends on how many visitors you have who open a page with the map.

And this was the invoice - for roughly 40,000 visitors per month:

So, my overall warning to you:

  • You need to put your Credit Card to use Google Maps API
  • But be extremely careful with maps usage cause you may get a huge bill

Now, if you're ready to proceed - you need to get Google Maps API key, and this is the page to start.

So you need to create an Application in Google Console, and enable some APIs. To achieve the example in this article, you need three APIs, here's the list from my dashboard.

Look at only those with numbers. You need:

  • Maps JavaScript API: to show embedded map on the page
  • Places API for Web: to provide auto-complete functionality for address
  • Geocoding API: to query the latitude/longitude by the address

Now, in this process you should get your API key, which will be the key to all of this. We put it in our Laravel project's .env file:

GOOGLE_MAPS_API_KEY=AIzaSyBi2dVBkdQSUcV8_uwwa**************

Warning again: As I mentioned, be extremely careful and not put that anywhere in the repository, cause you may get a bill for someone else's project. Or, better yet, restrict your API key by domain or some other method.

Now, we are ready to actually USE those maps!


Step 3. Filling Addresses/Coordinates in JavaScript

Our form is located in resources/views/admin/companies/create.blade.php file. I won't provide the full code, only the part that is actually relevant - an address field looks like this:

<div class="form-group">
    <label for="address_address">Address</label>
    <input type="text" id="address-input" name="address_address" class="form-control map-input">
    <input type="hidden" name="address_latitude" id="address-latitude" value="0" />
    <input type="hidden" name="address_longitude" id="address-longitude" value="0" />
</div>
<div id="address-map-container" style="width:100%;height:400px; ">
    <div style="width: 100%; height: 100%" id="address-map"></div>
</div>

As I mentioned before, three fields here and one map container to show.

Now, at the end of this Blade file, we add this JavaScript:

@section('scripts')
    @parent
    <script src="https://maps.googleapis.com/maps/api/js?key={{ env('GOOGLE_MAPS_API_KEY') }}&libraries=places&callback=initialize" async defer></script>
    <script src="/js/mapInput.js"></script>
@stop

Remember we added our API key to .env file? Here's the place we use it.

Notice: For this to work, in our main Blade layout file we need to have @yield('scripts') block.

Finally, we get to our public/js/mapInput.js where all the "magic" happens. This is the full code.

function initialize() {

    $('form').on('keyup keypress', function(e) {
        var keyCode = e.keyCode || e.which;
        if (keyCode === 13) {
            e.preventDefault();
            return false;
        }
    });
    const locationInputs = document.getElementsByClassName("map-input");

    const autocompletes = [];
    const geocoder = new google.maps.Geocoder;
    for (let i = 0; i < locationInputs.length; i++) {

        const input = locationInputs[i];
        const fieldKey = input.id.replace("-input", "");
        const isEdit = document.getElementById(fieldKey + "-latitude").value != '' && document.getElementById(fieldKey + "-longitude").value != '';

        const latitude = parseFloat(document.getElementById(fieldKey + "-latitude").value) || -33.8688;
        const longitude = parseFloat(document.getElementById(fieldKey + "-longitude").value) || 151.2195;

        const map = new google.maps.Map(document.getElementById(fieldKey + '-map'), {
            center: {lat: latitude, lng: longitude},
            zoom: 13
        });
        const marker = new google.maps.Marker({
            map: map,
            position: {lat: latitude, lng: longitude},
        });

        marker.setVisible(isEdit);

        const autocomplete = new google.maps.places.Autocomplete(input);
        autocomplete.key = fieldKey;
        autocompletes.push({input: input, map: map, marker: marker, autocomplete: autocomplete});
    }

    for (let i = 0; i < autocompletes.length; i++) {
        const input = autocompletes[i].input;
        const autocomplete = autocompletes[i].autocomplete;
        const map = autocompletes[i].map;
        const marker = autocompletes[i].marker;

        google.maps.event.addListener(autocomplete, 'place_changed', function () {
            marker.setVisible(false);
            const place = autocomplete.getPlace();

            geocoder.geocode({'placeId': place.place_id}, function (results, status) {
                if (status === google.maps.GeocoderStatus.OK) {
                    const lat = results[0].geometry.location.lat();
                    const lng = results[0].geometry.location.lng();
                    setLocationCoordinates(autocomplete.key, lat, lng);
                }
            });

            if (!place.geometry) {
                window.alert("No details available for input: '" + place.name + "'");
                input.value = "";
                return;
            }

            if (place.geometry.viewport) {
                map.fitBounds(place.geometry.viewport);
            } else {
                map.setCenter(place.geometry.location);
                map.setZoom(17);
            }
            marker.setPosition(place.geometry.location);
            marker.setVisible(true);

        });
    }
}

function setLocationCoordinates(key, lat, lng) {
    const latitudeField = document.getElementById(key + "-" + "latitude");
    const longitudeField = document.getElementById(key + "-" + "longitude");
    latitudeField.value = lat;
    longitudeField.value = lng;
}

As this blog is about Laravel and not JavaScript, I won't comment on it too much. It's pretty self-explainatory:

This part loads the map and puts the center marker:

const map = new google.maps.Map(document.getElementById(fieldKey + '-map'), {
            center: {lat: latitude, lng: longitude},
            zoom: 13
        });
const marker = new google.maps.Marker({
    map: map,
    position: {lat: latitude, lng: longitude},
});

This part is about autocompleting the address:

const autocomplete = new google.maps.places.Autocomplete(input);
autocomplete.key = fieldKey;
autocompletes.push({input: input, map: map, marker: marker, autocomplete: autocomplete});

Finally, this is querying for latitude and longitude:

geocoder.geocode({'placeId': place.place_id}, function (results, status) {
    if (status === google.maps.GeocoderStatus.OK) {

        const lat = results[0].geometry.location.lat();
        const lng = results[0].geometry.location.lng();

        setLocationCoordinates(autocomplete.key, lat, lng);
    }
});

For more details and examples, please read Google Maps API JavaScript documentation.

avatar

Good day Laravel Daily Team . Thanks for this insightful tutorial. I have tried to implement it but the data is not saving into the database. I would appreciate if you can assist me with where i am getting it wrong

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 58 courses (1056 lessons, total 44 h 09 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials