Skip to content

Week 11 Web Map

This week we will be completing your web map by adding data from Week 9 onto the custom base map we created in Week 10.

1. Set up your Development Environment

First, we need to get our code back from our Github Repository onto our laptops for editing. This is called setting up a Development Environment. Ours will not be complicated - we are simply going to download the files, and reupload them once we are done, and use a browser to test the index.html file.

For Tech-Hares!

If you're a wizz at git and interested in the experimental pathways, feel free to set up the site as a git using either Github Desktop or pull straight from the repostitory

Download your code

First, we need to download our code back onto our laptops/computers in visual studio code.

To do this, go to your repository and select Code, then Download Zip.

Move the c183-webmap.zip file somewhere you'll be able to find it, and then unzip (double click for MacOSX).

Bring it into Visual Studio Code

Now we need to open our files.

Go to Visual Studio Code (or download it here https://code.visualstudio.com/Download if you're using your laptop and it's not already downloaded).

Select Open Folder, and then select the folder you have just downloaded (called BAHA-Map or similar) ! [Image Title] (https://code.visualstudio.com/assets/docs/getstarted/getting-started/open-folder.png)

All three of your files - index.html, style.css and script.js should be there, alongside your /data folder.

For Tech-Tortoises

Your index.html file will look different to the one on Code Pen - this is because when you downloaded your code pen files, it automatically linked your style.css and script.js files into your index.html as it packaged them. It needs to do this so you html knows where to find your style and your javascript!

Check it still works!

On your desktop, locate your index.html file and drag it into a browser - you should see your map (as long as you're on the internet!).

DO NOT CLOSE THIS BROWSER WINDOW - we will be using this to test the updates to your map when we add your markers and such.

Create an "On Load" function

Before we add our markers and pop-ups, we need to make sure our markers don't load before our map. So, we are going to create an on('load') for our map. This will be a wrapper for all of our markers (and pop-ups) which will go inside the function.

It's made up of several parts.

map.on('load', function() {
});
Take a careful look: what is happening here is that when the map object (i.e. map) has an event (on) where it has loaded (load) then, run these pieces of code function() {}

You can see there are lots of syntax also nested in there - you have to be careful - make sure every bracket and parenthesis has a pair, and pay attention to the semi-colons going forward!

Now, copy and paste the function into your script.js file under your const map.

Your code should look something like this:

mapboxgl.accessToken = 'pk.eyJ1IjoiY3dpbG1vdHQiLCJhIjoiY2s2bWRjb2tiMG1xMjNqcDZkbGNjcjVraiJ9.2nNOYL23A1cfZSE4hdC9ew';
const map = new mapboxgl.Map({
        container: 'map', // container ID
        style: 'mapbox://styles/cwilmott/cmg5px11u00ef01sm3fr65ro0',
        center: [-122.27, 37.8], // starting position [lng, lat]. Note that lat must be set between -90 and 90
        zoom: 9 // starting zoom
    });

map.on('load', function() {

});

2. Add Markers

Now, we are going to add our markers inside our load function. To do this, we have to do two things: 1. Make a link to our geojson data 2. Add it as a marker (or symbol) layer to our webmap

a. Add Source Data

Adding source data is relatively easy. But first, we need to locate our source data!

There are two ways of creating links using code: one is what is called a relative path, in which the link structured according to its position to your code. In this cse, the relative path to your data source would be: data/yourdata.geojson (i.e. it's in the data folder, with your geojson). In most cases for web development, this is what is recommended because it contains all the links in the same structure of the program and makes it easy to move.

But today, I want to make sure it works, so I'm going to recommend we do an absolute path to the location of your raw data on the internet - in a large part because I've seen your repositories and they're all over the shop with naming and structure.

So, I want you to go to your Github repository, select your 183data.geojson file, and select raw. The link in the address bar is what you will need.

Now, , I want you to look at this piece of code:

1
2
3
4
map.addSource('points-data', {
        type: 'geojson',
        data: 'thefullwebaddressofyourdata.geojson'
    });
Can you see what is happening here? If what you think is happening is that we are adding a source (addSource) to the map (map), called 'points-data', which has the type 'geojson', and a link to the data - you are correct. 🔥 🔥 🔥 🔥

Now, see if you can paste the code inside the Load Function and then replace the address of the data with the address of YOUR data. (and don't worry, you won't see anything change, because we haven't added our markers).

If it looks something like this, you're doing great! AND PAY ATTENTION TO THAT SYNTAX!

1
2
3
4
5
6
map.on('load', function() {
    map.addSource('points-data', {
        type: 'geojson',
        data: 'https://raw.githubusercontent.com/cwilmott/c183-webmap/refs/heads/main/data/183-data.geojson'
    });
});
Nice! 🦖

Warning

You'll notice on the Mapbox documentation that a lot of the examples have the geoJSON code in the html, or directly in the javascript. This is one way to do it, but it takes far far longer to render for large datasets and is not as dynamic.

b. Add Your Markers / Symbol Layer

Okay, now we have added our source (addSource), we need to create a marker layer (addLayer) which puts a marker at each point in your data.

Mapbox has some handy configurations available which means we don't need to do the work of creating our own markers (right now at least!). I've coded up a basic circle marker like this:

map.addLayer({
        id: 'points-layer',
        type: 'circle',
        source: 'points-data',
        paint: {
            'circle-color': '#4264FB',
            'circle-radius': 6,
            'circle-stroke-width': 2,
            'circle-stroke-color': '#ffffff'
        }
    });
We chatted in lecture about the different elements of the marker - note the source data! That's important.

Copy and paste the above code into your Load Function under your addSource function. Order matters! If the data hasn't loaded, how will the markers know where to go!

Your code should now look something like this:

mapboxgl.accessToken = 'pk.eyJ1IjoiY3dpbG1vdHQiLCJhIjoiY2s2bWRjb2tiMG1xMjNqcDZkbGNjcjVraiJ9.2nNOYL23A1cfZSE4hdC9ew';
const map = new mapboxgl.Map({
        container: 'map', // container ID
        style: 'mapbox://styles/cwilmott/cmg5px11u00ef01sm3fr65ro0',
        center: [-122.27, 37.8], // starting position [lng, lat]. Note that lat must be set between -90 and 90
        zoom: 9 // starting zoom
    });

map.on('load', function() {
    map.addSource('points-data', {
        type: 'geojson',
        data: 'https://raw.githubusercontent.com/cwilmott/c183-webmap/refs/heads/main/data/183-data.geojson'
    });

    map.addLayer({
        id: 'points-layer',
        type: 'circle',
        source: 'points-data',
        paint: {
            'circle-color': '#4264FB',
            'circle-radius': 6,
            'circle-stroke-width': 2,
            'circle-stroke-color': '#ffffff'
        }
    });
});
Check it has worked by refreshing your index.html page in the browser. If it does, proceed! 🥂

For Tech-Hares

See if you can change the color, radius, stroke width and stroke color - have some fun, but don't lose that syntax!

3. Add Pop-Ups to Markers

Pop-Ups are the trickiest thing we'll do and there are a lot of moving parts - this is why we discussed it so much in lecture! You need to be careful, take note of the syntax, debug or go back a few steps - and pay attention to the little alerts that pop up in VS Code.

Error is not a failure in coding - it's a part of the process. I'm not going to even tell you how many error warnings I got learning to code.

Thankfully, like markers, Mapbox GL JS provides us with a pre-made pop-up option, which we can just amend.

Tip

For more information on mapbox popups see: https://docs.mapbox.com/mapbox-gl-js/api/markers/#popup

a. Create an "On Click" event, inside your "On Load" event.

First, we don't want pop-ups all the time, we only want them when we click. So we need to create an on click event inside our Load Event. It looks very much the same:

1
2
3
 map.on('click', 'points-layer', (e) => {

    });

Copy and paste the code inside your on load event. Your script.js so far should look a little like this:

mapboxgl.accessToken = 'pk.eyJ1IjoiY3dpbG1vdHQiLCJhIjoiY2s2bWRjb2tiMG1xMjNqcDZkbGNjcjVraiJ9.2nNOYL23A1cfZSE4hdC9ew';
const map = new mapboxgl.Map({
        container: 'map', // container ID
        style: 'mapbox://styles/cwilmott/cmg5px11u00ef01sm3fr65ro0',
        center: [-122.27, 37.8], // starting position [lng, lat]. Note that lat must be set between -90 and 90
        zoom: 9 // starting zoom
    });

map.on('load', function() {
    map.addSource('points-data', {
        type: 'geojson',
        data: 'https://raw.githubusercontent.com/cwilmott/c183-webmap/refs/heads/main/data/183-data.geojson'
    });

    map.addLayer({
        id: 'points-layer',
        type: 'circle',
        source: 'points-data',
        paint: {
            'circle-color': '#4264FB',
            'circle-radius': 6,
            'circle-stroke-width': 2,
            'circle-stroke-color': '#ffffff'
        }
    });

    // Add click event for popups
    map.on('click', 'points-layer', (e) => {

    });

});

b. Get the coordinates of where you have clicked

Next, we have to actually get the coordinates of where you have clicked, so it can be matched to your geojson data, so it knows which information to pull!

To do this, we create two constants - one for the coordinates, one for properties

      const coordinates = e.features[0].geometry.coordinates.slice();
      const properties = e.features[0].properties;

Copy and paste these inside your on click event like this:

1
2
3
4
5
6
// Add click event for popups
    map.on('click', 'points-layer', (e) => {
          const coordinates = e.features[0].geometry.coordinates.slice();
            const properties = e.features[0].properties;

    });

Make your code pretty again!

At this point, your code will be getting kind of messy. You can make it formatted again by selecting the keyboard shortcut Ctrl+Shift+I (Windows/Linux) or Cmd+Shift+I (macOS). It'll direct you to the command palette at the top - just write "Format Document" and press enter.

c. Design your popup.

Now we've retrieved the coordinates, and told the javascript where to find the propertes, we need to actually tell our popup what we want it to display using the actual properties from the data. We do this by creating a new constant const popupContent, which is a mix of html and javascript.

This is the code for my data:

const popupContent = `
            <div>
                <h3>${properties.Landmark}</h3>
                <p><strong>Address:</strong> ${properties.Address}</p>
                <p><strong>Architect & Date:</strong> ${properties.Architect_Date}</p>
                <p><strong>Designated:</strong> ${properties.Designated}</p>
                ${properties.Link ? `<p><a href="${properties.Link}" target="_blank">More Information</a></p>` : ''}
                ${properties.Notes ? `<p><strong>Notes:</strong> ${properties.Notes}</p>` : ''}
            </div>
        `;
You'll notice this piece of code where it says properties.XXX:

Address: $**{properties.Address}**

The "XXX" bit needs to be the exact same word / lettering / spacing / capitalization ** as YOUR data. These "XXX" refer to the names of the columns you created in geojson.io, and are the property labels in your raw data. You MUST double check each of these match - **especially Tech-Hares 🐇 who are more likely to have different labels due to the scraping process.

Copy and paste this into your on click function under your coordinate and property constants, so it looks like this:

 map.on('click', 'points-layer', (e) => {
        // Copy coordinates array
        const coordinates = e.features[0].geometry.coordinates.slice();
        const properties = e.features[0].properties;

        // Create popup content using the actual data properties
        const popupContent = `
            <div>
                <h3>${properties.Landmark}</h3>
                <p><strong>Address:</strong> ${properties.Address}</p>
                <p><strong>Architect & Date:</strong> ${properties.Architect_Date}</p>
                <p><strong>Designated:</strong> ${properties.Designated}</p>
                ${properties.Link ? `<p><a href="${properties.Link}" target="_blank">More Information</a></p>` : ''}
                ${properties.Notes ? `<p><strong>Notes:</strong> ${properties.Notes}</p>` : ''}
            </div>
        `;

    });

If your properties headers are not the same, change the code in your popip, not the headers in your geojson. If you're confused, come and grab us!

b. Add it to your map

Now, finally, we need to add the pop-up to the map at the location we have clicked. To do this, we can actually use a Mapbox GL JS framework which provided to us through the API: mapboxgl.Popup()

The code is simple: we create a new popup, set the x,y to the coordinates we collected above from the click, set the content of the popup to the const popupContent we created above, and add it to the map.

1
2
3
4
new mapboxgl.Popup()
            .setLngLat(coordinates)
            .setHTML(popupContent)
            .addTo(map);
Like before, we just add it to our on click event so it looks like this:

// Add click event for popups
    map.on('click', 'points-layer', (e) => {
        // Copy coordinates array
        const coordinates = e.features[0].geometry.coordinates.slice();
        const properties = e.features[0].properties;

        // Create popup content using the actual data properties
        const popupContent = `
            <div>
                <h3>${properties.Landmark}</h3>
                <p><strong>Address:</strong> ${properties.Address}</p>
                <p><strong>Architect & Date:</strong> ${properties.Architect_Date}</p>
                <p><strong>Designated:</strong> ${properties.Designated}</p>
                ${properties.Link ? `<p><a href="${properties.Link}" target="_blank">More Information</a></p>` : ''}
                ${properties.Notes ? `<p><strong>Notes:</strong> ${properties.Notes}</p>` : ''}
            </div>
        `;

        new mapboxgl.Popup()
            .setLngLat(coordinates)
            .setHTML(popupContent)
            .addTo(map);
    });
Reload the index.html in the web browser and test it out!

Broken Pop-Up Appearing from the bottom

If your pop-up is coming up from the bottom, you'll need to add the following code into you <head> tag in index.html, then save and reload: <link rel='stylesheet' href='https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.css'>

Easter egg! Changing the cursor

Tip

You don't have to do this to make the code work! So if it's getting late in the lab, don't worry!

If you're keen on trying just a little more JS, there is a fairly simple piece of code which we can add which changes the cursor to a pointer when we hover over a data point.

The code looks like this and it goes inside the Load function but outside the Click event:

1
2
3
4
5
6
7
8
9
 // Change cursor to pointer when hovering over points
    map.on('mouseenter', 'points-layer', () => {
        map.getCanvas().style.cursor = 'pointer';
    });

    // Change cursor back when leaving points
    map.on('mouseleave', 'points-layer', () => {
        map.getCanvas().style.cursor = '';
    });

Your Javascript will look like this:

mapboxgl.accessToken = 'pk.eyJ1IjoiY3dpbG1vdHQiLCJhIjoiY2s2bWRjb2tiMG1xMjNqcDZkbGNjcjVraiJ9.2nNOYL23A1cfZSE4hdC9ew';
const map = new mapboxgl.Map({
        container: 'map', // container ID
        style: 'mapbox://styles/cwilmott/cmg5px11u00ef01sm3fr65ro0',
        center: [-122.27, 37.8], // starting position [lng, lat]. Note that lat must be set between -90 and 90
        zoom: 9 // starting zoom
    });

map.on('load', function() {
    map.addSource('points-data', {
        type: 'geojson',
        data: 'https://raw.githubusercontent.com/cwilmott/c183-webmap/refs/heads/main/data/183-data.geojson'
    });

    map.addLayer({
        id: 'points-layer',
        type: 'circle',
        source: 'points-data',
        paint: {
            'circle-color': '#4264FB',
            'circle-radius': 6,
            'circle-stroke-width': 2,
            'circle-stroke-color': '#ffffff'
        }
    });

    // Add click event for popups
    map.on('click', 'points-layer', (e) => {
        // Copy coordinates array
        const coordinates = e.features[0].geometry.coordinates.slice();
        const properties = e.features[0].properties;

        // Create popup content using the actual data properties
        const popupContent = `
            <div>
                <h3>${properties.Landmark}</h3>
                <p><strong>Address:</strong> ${properties.Address}</p>
                <p><strong>Architect & Date:</strong> ${properties.Architect_Date}</p>
                <p><strong>Designated:</strong> ${properties.Designated}</p>
                ${properties.Link ? `<p><a href="${properties.Link}" target="_blank">More Information</a></p>` : ''}
                ${properties.Notes ? `<p><strong>Notes:</strong> ${properties.Notes}</p>` : ''}
            </div>
        `;

        new mapboxgl.Popup()
            .setLngLat(coordinates)
            .setHTML(popupContent)
            .addTo(map);
    });

    // Change cursor to pointer when hovering over points
    map.on('mouseenter', 'points-layer', () => {
        map.getCanvas().style.cursor = 'pointer';
    });

    // Change cursor back when leaving points
    map.on('mouseleave', 'points-layer', () => {
        map.getCanvas().style.cursor = '';
    });

});

Final Final Easter Egg - Add a Heading.

If you'd like to add a heading - here is some code to play with.

In your HTML <body>

<h1> Heritage Map of Berkeley</h1>
And in your style.css
h1 {
    position: absolute;
    top: 20px;
    left: 20px;
    z-index: 1000;
    background: rgba(255, 255, 255, 0.9);
    padding: 15px 20px;
    margin: 0;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif;
    font-size: 24px;
    color: #333;
}

Save both files, and reload index.html to make sure its worked! See if you can figure out how to change it - hahhhahahah!

Check your code and reupload to Github.

Now, back to reality. We are ready to reupload our code.

!!! tip "For Tech-Tortoises If you have not added a header, nothing in your HTML or CSS code should be different - so you can just copy and paste the code in your script.js file.

There are two ways of doing this. The first way, involves deleting your files and uploading the new ones.

The second, cheaty way - which I recommend - is to edit your Github files, by deleting the current code, and copying and pasting the code from VS Studio into Github directly.

See if you can figure out how to do this on your own - and call us over if you get stuck!

The Final Code

This is the final code, without the two easter eggs, if you get stuck.

=== "index.html"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
  ``` html
   <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Mapbox GL JS map</title>
      <link rel='stylesheet' href='https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.css'>
      <link rel="stylesheet" href="./style.css">
    </head>
    <body>
          <div id="map"></div>
          <script src='https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.js'></script><script  src="./script.js"></script>
    </body>
    </html>
  ```

=== "style.css"

1
2
3
4
``` css
  body { margin: 0; padding: 0; }
  #map { position: absolute; top: 0; bottom: 0; width: 100%; }
```

=== "script.js"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
  ``` js
  mapboxgl.accessToken = 'pk.eyJ1IjoiY3dpbG1vdHQiLCJhIjoiY2s2bWRjb2tiMG1xMjNqcDZkbGNjcjVraiJ9.2nNOYL23A1cfZSE4hdC9ew';
  const map = new mapboxgl.Map({
          container: 'map', // container ID
          style: 'mapbox://styles/cwilmott/cmg5px11u00ef01sm3fr65ro0',
          center: [-122.27, 37.8], // starting position [lng, lat]. Note that lat must be set between -90 and 90
          zoom: 9 // starting zoom
      });

  map.on('load', function() {
      map.addSource('points-data', {
            type: 'geojson',
            data: 'https://raw.githubusercontent.com/cwilmott/c183-webmap/refs/heads/main/data/183-data.geojson'
      });

     map.addLayer({
        id: 'points-layer',
        type: 'circle',
        source: 'points-data',
        paint: {
              'circle-color': '#4264FB',
              'circle-radius': 6,
              'circle-stroke-width': 2,
              'circle-stroke-color': '#ffffff'
          }
      });

      // This is the click event for popUps
      map.on('click', 'points-layer', (e) => {
          // Get coordinates/geometry
          const coordinates = e.features[0].geometry.coordinates.slice();
          const properties = e.features[0].properties;

          // Create popup content using the properties from the data
           const popupContent = `
              <div>
                  <h3>${properties.Landmark}</h3>
                  <p><strong>Address:</strong> ${properties.Address}</p>
                  <p><strong>Architect & Date:</strong> ${properties.Architect_Date}</p>
                  <p><strong>Designated:</strong> ${properties.Designated}</p>
                  ${properties.Link ? `<p><a href="${properties.Link}" target="_blank">More Information</a></p>` : ''}
                  ${properties.Notes ? `<p><strong>Notes:</strong> ${properties.Notes}</p>` : ''}
              </div>
    `      ;
        // Build and attach popup to coordinates
          new mapboxgl.Popup()
              .setLngLat(coordinates)
              .setHTML(popupContent)
              .addTo(map);
      });

      // Change cursor to pointer when hovering over points
      map.on('mouseenter', 'points-layer', () => {
              map.getCanvas().style.cursor = 'pointer';
      });

      // Change cursor back when leaving points
      map.on('mouseleave', 'points-layer', () => {
            map.getCanvas().style.cursor = '';
      });       
  });
  ```