The Pudding recently published a fantastic travel-like-a-local guide. Using that article as inspiration, today we will create a map that will mark the attractions in San Francisco.
For this tutorial, we will be utilizing the following tools:
A few years ago, I took a class called Data Journalism. This course was designed for journalism students and essentially introduced me to HTML, CSS, JavaScript, and Leaflet.
In a semester, I not only learned the basics of programming, but I also built a web application that showcased the schools in San Francisco County.
Why am I mentioning this? Because if you understand the basics of HTML, CSS, and JavaScript, you can pick up Leaflet.
Leaflet is a JavaScript library for creating mobile-friendly interactive maps. It’s a widely used JS library, and chances are, you’ve seen Leaflet on NPR or WaPo or SFChronicle.
A well-documented API is always helpful when working with a new library or framework. Luckily, Leaflet is one of the libraries that has easy-to-follow documentation.
I recently started working with maps a lot, using different tools (D3, Leaflet, and Mapbox), and I’ve wondered what data we need to create a map. I’ll be talking about a few of those here.
One common aspect among the three tools is a placeholder for a map. We need a <div>
, usually with an id
, on top of which we will mount our map.
We also need to define the center — the geographic center of the map and the zoom level — as the initial map zoom level.
<div id="mapContainer"></div> L.map("mapContainer").setView([37.7749, -122.4194], 13); //places the map in San Francisco.
If you wanted to map Paris but you only see water, chances are, you’ve switched the center coordinates. Always double-check them.
We also need to create a layer.
What is a layer? If you’ve used Google Maps, you might use Traffic layer or Satellite layer. You may use Terrain layer or a default layer. These are different kinds of layers you can place on top of a map.
For our example, we will be using the Mapbox Streets tile layer. To work with Mapbox, we will need to request an access token. Go ahead and get an access token here.
Once we have the token, we can add our layer to the map like this:
L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', { attribution: 'Map data (c) <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery (c) <a href="https://www.mapbox.com/">Mapbox</a>', maxZoom: 18, id: 'mapbox/streets-v11', accessToken: 'myaccessToken' }).addTo(mapContainer);
This is all we need to have a basic map.
Let’s see how we can create this map using Vue.
Vue.js, with 168K stars on GitHub, is a JavaScript-based frontend framework for building user interfaces.
This article will assume you’ve got a working knowledge of Vue. If you need a refresher, the official documentation is very well maintained and easy to understand.
Before we get into coding our map, let’s install some dependencies.
Let’s also install Leaflet.
Following the directions here, we can execute the following command in our terminal:
npm install leaflet
If you remember, we said in the beginning that we need a div
to mount our map. Let’s add a div
in our template and give it an id
of mapContainer
.
<template> <div id="container"> <div id="mapContainer"></div> </div> </template>
In our script, we will first import Leaflet like this:
import "leaflet/dist/leaflet.css"; import L from "leaflet";
Make sure to import the CSS. Without it, the map won’t work correctly.
<script> import "leaflet/dist/leaflet.css"; import L from "leaflet"; export default { name: "Map", data() { return{ center: [37,7749, -122,4194] }} methods: { setupLeafletMap: function () { const mapDiv = L.map("mapContainer").setView(this.center, 13); L.tileLayer( "https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}", { attribution: 'Map data (c) <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery (c) <a href="https://www.mapbox.com/">Mapbox</a>', maxZoom: 18, id: "mapbox/streets-v11", accessToken:”XXX", } ).addTo(mapDiv); }, }, mounted() { this.setupLeafletMap(); }, }; </script> <style scoped> #mapContainer { width: 80vw; height: 100vh; } </style>
Once we have our imports, we will give our component a name. We have named it Map. You can give your component any name you like.
We need one piece of information for this app to work — the center of our map. We have added a center property in our data project.
Then we will create a function, setupLeafletMap
, inside our methods object. As the name suggests, this function will set up our map when the component mounts.
We have used the same code we used before to initialize the map.
We could have directly added the code inside the mounted function, but to keep the code organized, we created a separate function that we will call inside the mounted function.
Make sure to give your mapContainer
some width and height. Run yarn serve
, and you should see the same map.
Let’s add some data to our app and make it more interactive. To display anything on the map, we need coordinates of that location. Luckily, we can get the data from San Francisco Open Data.
Let’s search for the historic landmarks in the portal and click View Data. As we can see, the first column defines the geometry for all locations.
The other columns contain the name, street name, street type, and address, among other details.
Usually, if you click on the three dots on the top of every column, they provide the description.
For this dataset, the description is missing.
We could work with the API endpoint, but we will download the GEOJSON data for this tutorial. Based on JSON, GeoJSON is a format used to encode a variety of geographic data structures.
If you want to work with the API instead, click on export and then SODA API. To use the API, you’d need an access token. Getting an access key is simple. Make an account here, and click on the manage link towards the bottom right.
Once there, follow the steps and copy your Key ID and Key Secret.
Let’s save our downloaded file as JSON and import it in our component:
import data from "./Historic-Landmarks.json";
The Leaflet’s API we need is called L.geoJSON()
.
According to the documentation, “L.geoJSON() allows you to parse GeoJSON data and display it on the map.”
Let’s pass our data to the function, and add it to the map using addTo()
.
L.geoJSON(data).addTo(mapDiv);
If we refresh our page, we should see blue polygons on our map. Each polygon represents an attraction in the city.
Let’s play with some of the geoJSON()
options. We don’t have a lot of data for each location, so just to see how to style option works, we will use the value date listed to style our polygons.
We will pass a function that will style all the locations listed after the year 2000 red and the rest blue. Since the date listed is a string, we will need to do some conversion and slicing to get the year.
Let’s create a function called styleMap
.
styleMap(feature){ const year = feature.properties.datelisted ? parseInt(feature.properties.datelisted.slice(0, 4)) : 0; const color = year > 2000 ? "red" : "blue"; return { color: color }; },
If we want to filter the data, we could use a filter option instead.
Lastly, let’s add some interactions. When the user clicks on the polygon, a popup will appear. Leaflet provides us with a function called onEachFeature
.
According to the documentation:
“The onEachFeature
option is a function that gets called on each feature before adding it to a GeoJSON layer. A common reason to use this option is to attach a popup to features when they are clicked.”
We will create another function called onEachFeature
. We only want to show the name on our popup, so we will see if it exists. If it does, we will use the layer.bindPopup()
and pass in the name.
onEachFeature(feature, layer) { if (feature.properties && feature.properties.name) { layer.bindPopup(feature.properties.name); layer.on('mouseover', () => { layer.openPopup(); }); layer.on('mouseout', () => { layer.closePopup(); }); } },
If we refresh our app and click on Golden Gate Bridge, a popup will appear with the name on it.
Let’s pass both of our functions like this:
L.geoJSON(data , {onEachFeature: this.onEachFeature,style: this.styleMap,})
We can update our popup to have more than just a name. Lastly, if you want your map to do more, you can always add click event.
You should now have a basic understanding of how Leaflet works with Vue. Leaflet allows you to do much more with your map. You can combine it with other APIs and show details — reviews and images, for example — about the locations.
Yelp, Foursquare, or Google are some of the few APIs you can play with. If you want to get started with the Foursquare API, check out the code here.
Another idea would be to add fancy markers on your map. Take a look at San Francisco Chronicle’s Fire Tracker. Maybe try experimenting with the Interactive Choropleth Map.
Be sure to check out Leaflet’s tutorial for inspiration and more APIs.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
2 Replies to "Building an interactive map with Vue and Leaflet"
The code seems to be seriously incomplete. You define two functions that are never called, and you’re loading the GEO Json data into this.map, but that variable is never defined. You should post the complete, functional code, at least for download.
Hi Michael! Thank you for reading and pointing out the error. It should be `mapDiv` instead of `this.map`. The two functions that you are talking about are the options for `L.geoJSON(data)`. I should have been more clear about it. Those updates have been made.
The code is already linked in the conclusion.