Plotting data - rochiecuevas/USGS-earthquakes GitHub Wiki

JavaScript libraries D3 and Leaflet were used to bring life to the USGS dataset. The code described below leads to the development of the automatically updating and interactive earthquake map.

The crucial steps in creating this interactive map is the development of a function that creates the layer group of earthquake markers and the development of a function that creates maps using this layer group and base layers. Once these are set, the data from the USGS can be plugged in and Today's Earthquakes can be visualised.

Resources/logic.js

The data comes from the USGS earthquake API.

// Define the USGS earthquake data URL
var quakeURL = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson";

Create the marker layer containing earthquake markers

The data from quakeURL would eventually be passed to a function called "createFeatures". This function assigns a marker to the coordinates of each earthquake. The group of markers eventually become a map layer distinct from the base layers of the map. To create the layer of markers, the first step in the function is to create an empty array.

function createFeatures(earthquakeData) {
    // (1) Create an empty array
    var earthquakeMarkers = []; 
}; 

Second, it uses a function called "customColors" to assign colours to the earthquakes based on their magnitudes.

function createFeatures(earthquakeData) {
    // (1) Create an empty array

    // (2) Create a function that will be used to colour the markers
    function customColor(radius) {
        if (radius <= 2) {return "#ffffb2"} 
        else if (radius <= 4) {return "#fed976"}
        else if (radius <= 5) {return "#feb24c"}
        else if (radius <= 6) {return "#fd8d3c"}
        else if (radius <= 7) {return "#fc4e2a"}
        else if (radius <= 8) {return "#e31a1c"}
        else {return "#b10026"}
    }; 
}; 

Notice that the range has been hard-coded. The earthquake magnitude range is from 1 to >8. Hence, to provide an accurate visual representation of earthquake magnitude range, rather than one that dynamically changes based on the values obtained from the past 24 hours, the range values were hard-coded. The colours were set manually as well, based on the yellow to red colour palette.

Third, it loops through the earthquake data and assigns a circle marker to each earthquake coordinate.

function createFeatures(earthquakeData) {
    // (1) Create an empty array
    // (2) Create a function that will be used to colour the markers

    // (3) Loop through the earthquakeData array...
    for (var i = 0; i < earthquakeData.length; i ++){

        // (3a) Create a circle marker for each earthquake coordinate with radius proportional 
        // to the magnitude of the earthquake
        var marker = L.circleMarker([earthquakeData[i].geometry.coordinates[1],  // latitude
                                     earthquakeData[i].geometry.coordinates[0]]); // longitude
    };
};

Notice that the circle marker creates an array of coordinates. Because the JSON object lists longitude before latitude, the order has been edited in the array of each circle marker.

And because each circle marker has to reflect the magnitude of each earthquake based on colour and radius, additional specifications were added. The radius was magnitude multiplied by a factor of three to make them more visible in the world map.

var marker = L.circleMarker([earthquakeData[i].geometry.coordinates[1],
                                     earthquakeData[i].geometry.coordinates[0]]{

    // Use customColor function to assign colours to circles based magnitude
    color: customColor(earthquakeData[i].properties.mag),
    fillColor: customColor(earthquakeData[i].properties.mag),
    fillOpacity: 1,

    // Radius of each circle is based on the magnitude of the earthquake
    radius: earthquakeData[i].properties.mag * 3
    });

For each earthquake, a tooltip/pop-up was added such that a reader clicking on the circle would see more information about it (i.e., location, magnitude, depth, significance, time of occurrence).

var marker = L.circleMarker([earthquakeData[i].geometry.coordinates[1],
                             earthquakeData[i].geometry.coordinates[0]]{<see specs above>})

    // Chain a tooltip for each circle containing more information about the earthquake
    .bindPopup("<h3>" + earthquakeData[i].properties.place + 
              "</h3><hr><p>Magnitude: " + earthquakeData[i].properties.mag + 
              "<br>Depth: " + earthquakeData[i].geometry.coordinates[2] + "km
              <br>Significance: " + earthquakeData[i].properties.sig + 
              "<br>Time: " + new Date(earthquakeData[i].properties.time)+ "</p>");

Each marker (following colour and size specifications; accompanied by a tooltip) was then added to the empty list created in step 1.

function createFeatures(earthquakeData) {
    // (1) Create an empty array
    // (2) Create a function that will be used to colour the markers

    // (3) Loop through the earthquakeData array...
        for (var i = 0; i < earthquakeData.length; i ++){

           // (3a) Create a circle marker...
            var marker = L.circleMarker([earthquakeData[i].geometry.coordinates[1],
                                         earthquakeData[i].geometry.coordinates[0]]{<see specs above>})
                         .bindPopup(<additional earthquake information>);

           // (3b) Add each marker to the empty array
           earthquakeMarkers.push(marker);
       };    
};

The fourth step is creating a layer group containing the markers. This makes adding overlay layers easy because markers do not need to be added removed individually.

function createFeatures(earthquakeData) {
    // (1) Create an empty array
    // (2) Create a function that will be used to colour the markers
    // (3) Loop through the earthquakeData array, create markers representing earthquakes

    // (4) Create an earthquake layerGroup
    var earthquakes = L.layerGroup(earthquakeMarkers);
};

Lastly, the createFeatures function sends the earthquake marker layer to another function called "createMap".

function createFeatures(earthquakeData) {
    // (1) Create an empty array
    // (2) Create a function that will be used to colour the markers
    // (3) Loop through the earthquakeData array, create markers representing earthquakes
    // (4) Create an earthquake layerGroup

    // (5) Send the earthquakes layer to the createMap function
    createMap(earthquakes);
};

Create the map

The world map consists of four layers: three base layers and one overlay layer. It also contains a legend. Building it, therefore, requires several steps. This is done using the "createMap" function, which calls the layer group developed using the createFeatures function.

The first step is to create the tile layers. There are three tile layers for the map in Today's Earthquakes and the code to generate the streetMap tile layer is shown below as an example.

function createMap(earthquakes) {

// (1) Define the tile layers
var streetMap = L.tileLayer("https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}", {
attribution: "Map data &copy; <a href=\"https://www.openstreetmap.org/\">OpenStreetMap</a> contributors, <a href=\"https://creativecommons.org/licenses/by-sa/2.0/\">CC-BY-SA</a>, Imagery © <a href=\"https://www.mapbox.com/\">Mapbox</a>",
maxZoom: 18,
id: "mapbox.streets", // can be changed (see Table 1)
accessToken: API_KEY
});

var satelliteMap;
var darkMap;
};

The tile layer is sourced from the Mapbox API, whose link is seen above. To access it requires getting an API key. This can be obtained upon registration at Mapbox's website and stored in the config.js file. This file is not in the public repository for security purposes. The {id} refers to the type of tile layer. In this project, three tile layers were used. Hence, defining the three tile layers just means changing the id specification in the script above (Table 1). Check out the options in Mapbox maps.

Table 1. id's for the different mapbox tile layers used in the project

tile layer variable {id}
var streetMap "mapbox.streets"
var darkMap "mapbox.dark"
var satelliteMap "mapbox.satellite"

The second step in createMap is defining the base layer. It involves creating an object whose keys are the designations of each tile layer and the values are the tile layer variables.

function createMap(earthquakes) {
    // (1) Define the tile layers

    // (2) Define a baseMap that contains the three tile layers
    var baseMap = {
        "Street Map": streetMap,
        "Satellite Map": satelliteMap,
        "Dark Map": darkMap
    };
};

The third step is defining the overlay map. This involves creating an object whose key is the label for the earthquake markers layer and the value is the earthquakes variable.

function createMap(earthquakes) {
    // (1) Define the tile layers
    // (2) Define a baseMap that contains the three tile layers

    // (3) Create an overlayMap that contains the earthquake markers layer
    var overlayMap = {
        "Earthquakes Today": earthquakes
    };
};

The fourth step is setting up the map. Because the map visualises global earthquake information, it is best to put the Atlantic Ocean in the centre of the map. Hence, the Americas are on the left side; Europe, Africa, Asia, and Oceania are on the right; and Antarctica is at the bottom. Also, the map zoom setting is at 2, which allows a wider view of the world map.

function createMap(earthquakes) {
    // (1) Define the tile layers
    // (2) Define a baseMap that contains the three tile layers
    // (3) Create an overlayMap that contains the earthquake markers layer
    
    // (4) Create the map, centred somewhere near 20ºN and 10ºE
    var MapCoords = [20, 10];
    var mapZoomLevel = 2;

    var map = L.map("map", {
        center: MapCoords,
        zoom: mapZoomLevel,
        layers: [satelliteMap, darkMap, streetMap, earthquakes]
    });
};

Note that the different map layers are placed in an array, without specifying which ones are the base and the overlay layers. The fifth step is by creating layer control, so that the base layers can be selected using radio buttons (which means that readers cannot remove a base layer; hence a map is always present), and the overlay layer is selected using a tick box (which means that readers can choose to remove the overlay from the map).

function createMap(earthquakes) {
    // (1) Define the tile layers
    // (2) Define a baseMap that contains the three tile layers
    // (3) Create an overlayMap that contains the earthquake markers layer
    // (4) Create the map, centred somewhere near 20ºN and 10ºE

    // (5) Create a layer control to pass the baseMap and the overlayMap and add it to the map
    var control = L.control.layers(baseMap, overlayMap, {
        collapsed: false
    });

    control.addTo(map);
};

The map is becoming operational! It now theoretically contains the markers of different sizes and colours. It also has three base layers for readers to choose from. Also, it updates every five minutes, ensuring that readers have the latest earthquake information on their screens, once the earthquake data has been accessed from the quakeURL. However, to know the magnitude of an earthquake, the reader still needs to click on its marker and read the tooltip. To make sure that an estimation can be made based on the colour, a legend is added at the bottom right side of the map. The code for the legend has been adapted from https://stackoverflow.com/a/31044581.

function createMap(earthquakes) {
    // (1) Define the tile layers
    // (2) Define a baseMap that contains the three tile layers
    // (3) Create an overlayMap that contains the earthquake markers layer
    // (4) Create the map, centred somewhere near 20ºN and 10ºE
    // (5) Create a layer control to pass the baseMap and the overlayMap and add it to the map

    // (6) Create a legend and add it to the map
    var legend = L.control({position: "bottomright"});
}; 

When this legend is added, it creates a new <div> HTML tag with the "info-legend" class selector. The text for the magnitude classes are found in the "categories" array while the colours used in the "customColor" function are listed in the "colours" array. The title of the legend is contained in the "labels" array. The <strong> HTML tags make the title's font bold.

var legend = L.control({position: "bottomright"});

legend.onAdd = function() {

    // (6a) Define the legend div and the arrays
    var div = L.DomUtil.create("div", "info-legend");
    var categories = ["1–2", "2–4", "4–5", "5–6", "6–7", "7–8", "<8"]
    var colours = ["#ffffb2", "#fed976", "#feb24c", "#fd8d3c", "#fc4e2a", "#e31a1c", "#b10026"];
    var labels = ['<strong>Magnitude:</strong>'];
};

But the mere defining of arrays does not create the legend. To create the legend with a vertical stack of coloured boxes and magnitude classes beside it, a for loop is used within the function that adds the legend.

var legend = L.control({position: "bottomright"});

legend.onAdd = function() {
    // (6a) Define the legend div and the arrays

    // (6b) Loop through the categories 
    for (var j = 0; j < categories.length; j++) {};
};

This for-loop creates a square colour block for each element in the "categories" array and puts the corresponding categories element (magnitude) beside it. The colour block-magnitude pair is then added to the inner HTML of the info-legend div and to the labels array.

// (6b) Loop through the categories... 
for (var j = 0; j < categories.length; j++) {

    // Place a square on the left for each category in the inner HTML, then add to the labels array
    // The square is a colour block, hence there is no text between <j></j>
        div.innerHTML += labels.push(
            '<j style = "background:' + colours[j] + '"></j>' + categories[j]
        );
};

The labels array becomes populated, in addition to the first element (the legend title). To create the vertical stack of colour squares and magnitudes, the labels were "joined" at the line break (<br>).

legend.onAdd = function() {
    // (6a) Create the legend div and define the arrays which are the sources of information for the legend div
    // (6b) Loop through the categories, add the colour box-magnitude combinations to the labels array, div inner HTML
   
    // (6c) Create a vertical stack of coloured boxes and labels
    div.innerHTML = labels.join('<br>');
};

Ultimately, the function used to add the legend returns the created and populated legend div.

legend.onAdd = function() {
    // (6a) Create the legend div and define the arrays which are the sources of information for the legend div
    // (6b) Loop through the categories, add the colour box-magnitude combinations to the labels array, div inner HTML
    // (6c) Create a vertical stack of coloured boxes and labels
    // (6d) Function outputs the created and populated div
};

The legend is then added to the map.

function createMap(earthquakes) {
    // (1) Define the tile layers
    // (2) Define a baseMap that contains the three tile layers
    // (3) Create an overlayMap that contains the earthquake markers layer
    // (4) Create the map, centred somewhere near 20ºN and 10ºE
    // (5) Create a layer control to pass the baseMap and the overlayMap and add it to the map
    // (6) Create a legend and add it to the map

    // (7) Add legend to the map
    legend.addTo(map);
};

The map is complete. To populate the earthquake markers, it is now time to access the USGS data.

// Perform a GET request to the USGS API endpoint
d3.json(quakeURL, function(error, response){
    if (error) throw error;

    // Send the response.features (array) to the createFeatures function
    var earthquakeData = response.features
    createFeatures(earthquakeData); // response.features = earthquakeData
});
⚠️ **GitHub.com Fallback** ⚠️