Installing OpenLayers with Laravel and creating our first map

A free screencast (video) course is available for this post but you need to be signed in order to view it, you can sign in here if you already have an account or register here if you don't have one.

We now have a dashboard to work with, make sure you take a look at Laravel Jetstream docs.

OpenLayers in Laravel

  • Let's first replace the Jetstream welcome component with our own new map anonymous component:
mkdir resources/views/components
touch resources/views/components/map.blade.php

Create a temporary map placeholder in the component blade file:

<div>
    <div class="map h-[600px] border border-slate-300 rounded-md shadow-lg">
			The map will be here
    </div>
</div>

Replace the following line in the resources/views/dashboard.blade.php file:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Dashboard') }}
        </h2>
    </x-slot>
    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
		    	<x-jet-welcome />
                <x-map />
            </div>
        </div>
    </div>
</x-app-layout>
  • Open a new terminal window in the project directory and run "dr npm run build"
  • Open your browser at "http://localhost:8080/dashboard" and should see the following placeholder:

OpenLayers in Laravel

  • Let's install OpenLayers npm dependencies:
dr npm i ol
  • Create a components folder in resources/js and another one in resources/css and add a map.js and map.css file in it:
mkdir resources/js/components
mkdir resources/css/components
touch resources/js/components/map.js
touch resources/css/components/map.css

resources/js/components/map.js content:

// we are using imports so we don't have to ship all of the openlayers library, only the components
// we actually use.
import Map from "ol/Map.js";
import View from "ol/View.js";
import TileLayer from "ol/layer/Tile.js";
import OSM from "ol/source/OSM.js";

// wait until Alpine.js is initialized and create our component function called map
document.addEventListener("alpine:init", () => {
    Alpine.data("map", function () {
        return {
            map: {},
            init() {
                // a openlayers map has a target (a html dom element), layers and a view
                // we initialise the map using x-ref="map" on the element and referencing it 
                // with the magic method this.$refs.map in our alpine component, this will
                // allow for multiple component on the same page.
                // our map also has a TileLayer (we will see the difference between a TileLayer and 
                // a VectorLayer in the next post. for our first map, we will use the OpenStreetMap
                // source for the layer.
                // finally, the maps' view will be centered to [0, 0] coordinates of the EPSG:4326 (WGS84)
                // projection at a zoom level of 2. we use the WGS84 projection because it's the one used
                // by GPSs in Latitude/Longitude. we also use it because we will later store our spatially
                // indexed data in postgis with this projection.
                this.map = new Map({
                    target: this.$refs.map,
                    layers: [
                        new TileLayer({
                            source: new OSM(),
                        }),
                    ],
                    view: new View({
                        projection: "EPSG:4326",
                        center: [0, 0],
                        zoom: 2,
                    }),
                });
            },
        };
    });
});

resources/css/components/map.css content:

@import 'ol/ol.css';
  • Make the following changes to the vite.config.js file:
import { defineConfig } from 'vite';
import laravel, { refreshPaths } from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/css/components/map.css',
                'resources/js/app.js',
                'resources/js/components/map.js',
            ],
            refresh: [
                ...refreshPaths,
                'app/Http/Livewire/**',
            ],
        }),
    ],
});
  • Now, let's adjust the layout file to use blade stacks to import our dependencies only when we need them, make the following changes to the resources/views/layouts/app.blade.php:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>{{ config('app.name', 'Laravel') }}</title>

        <!-- Fonts -->
        <link rel="stylesheet" href="https://fonts.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap">

        <!-- Scripts -->
        @stack('styles')
        @stack('scripts')
        @vite(['resources/css/app.css', 'resources/js/app.js'])

        <!-- Styles -->
        @livewireStyles
    </head>
    <body class="font-sans antialiased">
        <x-jet-banner />

        <div class="min-h-screen bg-gray-100">
            @livewire('navigation-menu')

            <!-- Page Heading -->
            @if (isset($header))
                <header class="bg-white shadow">
                    <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
                        {{ $header }}
                    </div>
                </header>
            @endif

            <!-- Page Content -->
            <main>
                {{ $slot }}
            </main>
        </div>

        @stack('modals')

        @livewireScripts
    </body>
</html>
  • Finally, push our scripts dependencies from our resources/components/map.blade.php file:
<div>
    <div class="map h-[600px] border border-slate-300 rounded-md shadow-lg">
// Instanciate the alpine component with the fonction previously created
<div x-data="map()">
// Use the x-ref to reference our map, this way, we can have multiple component on the page if needed
    <div x-ref="map" class="map h-[600px] border border-slate-300 rounded-md shadow-lg">
    </div>
</div>

@once
    @push('styles')
        @vite(['resources/css/components/map.css'])
    @endpush
    @push('scripts')
        @vite(['resources/js/components/map.js'])
    @endpush
@endonce
  • Make sure your you run "dr npm run build" again; we should now have an empty working map with OpenStreetMap working:

Stay along; in the next post, we will start adding and styling custom vector data to the map.

The commit for this post is available here (the content of the .env file is commited in the .env.example file): openlayers-in-laravel

First published 2 years ago
Latest update 1 year ago
Alex Millar
Posted by Alex Millar 1 year ago

Hello world! Just stumbled upon your site, an I'm currently working on building out a livewire component for a map (using Mapbox.com), I'll start diving into these guides over the next few days and let you know if I have any feedback. Awesome stuff.


webgisdev
Posted by webgisdev 1 year ago

Hey Alex!

Glad to have a fellow Canadian here! Let me know if you have any questions or comments!

Cheers!

GIS@GBTel
Posted by GIS@GBTel 1 year ago

Hey Webgisdev,

I'm having some issues with Alpine.JS throwing errors due to async problems?

I found this article on github & this one elaborating on the issue(s), did you have any recommendations to mitigate async call problems?

here's an image of my rendered webpage: Rendered Map


webgisdev
Posted by webgisdev 1 year ago

Hello,

Can you share the error you have in your browser's Developers Tools in the console? The project has not been using any async calls with Alpine.js until now.

Thanks!

GIS@GBTel
Posted by GIS@GBTel 1 year ago

@WebgisDev

Yeah, here is my console dump:

Console dump

webgisdev
Posted by webgisdev 1 year ago

Hello,

It looks like the map.js file is not loaded properly or is having a problem. Make sure you have this code in exactly in the map.js file:

(...)
document.addEventListener("alpine:init", () => {
    Alpine.data("map", function () {
        return {
            map: {},
            init() {
(...)

Also make sure that the map.js script load in you browser, this code in the layout file:

        <!-- Scripts -->
        @stack('styles')
        @stack('scripts')
        @vite(['resources/css/app.css', 'resources/js/app.js'])

And this code at the end of your map.blade.php file:

@once
    @push('styles')
        @vite(['resources/css/components/map.css'])
    @endpush
    @push('scripts')
        @vite(['resources/js/components/map.js'])
    @endpush
@endonce

And take a look at the Network tab in the browser's Developers Tools to make sure map.js is properly loaded. Also, don't forget to run "dr npm run build" before trying in the browser.

Tell me if it works!

Cheers!

GIS@GBTel
Posted by GIS@GBTel 1 year ago

Hey webgisdev,

I have confirmed that my scripts are per the lesson. But yet, I'm still having no data appear in the container.

Here is my code:

map.js [JS] map.blade.php [Blade View] app.blade.php [Layout] index.blade.php[Controller View]

Do you have any other suggestions?

GIS@GBTel
Posted by GIS@GBTel 1 year ago

If you still use IRC , you could add me on Element/Matrix if communicating through your comment section isn't ideal.

@zerodarkalpha:matrix.org

webgisdev
Posted by webgisdev 1 year ago

Hey!

Looks like you are missing the base scripts in your layout (@vite(['resources/css/app.css', 'resources/js/app.js'])). They have to come after the @stack('scripts') line.

Cheers!

ckayhernandez
Posted by ckayhernandez 8 months ago

Currently having this error

InvalidArgumentException

PHP 8.2.17

10.48.3

Unable to locate a class or view for component [jet-banner].


No response yet
You need to be signed in to post comments, you can sign in here if you already have an account or register here if you don't.