The world’s leading publication for data science, AI, and ML professionals.

Presenting Spatial Data Interactively by Scrolling

Telling stories/insights with responsive maps. Stories with maps!

Telling stories and insights with interactive maps

About this Article

This article is about a story map I produced:

…and this article is the story of how I built it.

Introduction

A cartographer’s products are maps. We work with shapefiles (which is the legacy of spatial data storage), geodatabases, geojsons, and many form of spatial data and use GIS to produce maps out of them. These maps, however, are often JPEGs, PNG, or PDF documents which are not interactive. For most cases, yes it works, but I like telling and being told stories where I don’t have to do much of the work. With less effort, how can I get told stories?

Then I encountered Cuthbert Chow‘s article about D3.js. D3.js is a javascript library to manipulate elements and svg in the DOM (Document Object Model). People use D3.js to present data, in which Chow has done magnificant demonstration, extending Jim Vallandingham’s article. Look at his following article.

How I Created an Interactive, Scrolling Visualisation with D3.js, and how you can too

And then it came down to me: we can make this with maps too! The idea of scrolling and having the data being presented interactively is possible using javascript. This article tells the story of how I did it.

You can find the live demo here:

In London

Stacks

All data and map resources are open sourced (openstreetmap). The following are the stacks I’ve used

  • jquery and d3.js : document manipulation
  • leaflet.js : interactive web-map

Generic Idea

I write about spatial Data Science, and I wrote an article about presenting spatial data in the following article. This article is the demonstration.

Spatial Data Science: Javascript & Python

Essentially the idea of how it works is:

  • Using Python to acquire and analyze spatial data.
  • Using HTML to lay out the documents, CSS to make things beautiful; presenting the content.
  • Using Javacript to make things interactive. In this case, change the maps when we scroll.

Additionally (not in this article/project), we can do something regarding the data sources:

  • Storing data in postgresql + postgis database server.
  • Apache Airflow to orchestrate/automatically acquire data and populate the database server.
  • Using geoserver as a backend map server.

Perhaps for future projects.

How it Works

I won’t cover the details, or this article will become very long. What I will explain is the high level idea for developers to… develop.

Most of the code comes from Jim’s article about JavaScript scroller. The demo based on this code is available here.

So You Want to Build A Scroller

But in this article I am discussing my demo. I will break it down based on these components:

  • Layout
  • Sections and Scrolling
  • Data Storage

The Layout – Fixing the Map

The layout is based on Bootstrap 5 css module. This is a very common module that beautifies HTML quickly. It provides the essential minimal UI components.

Specifically, I made 2 columns, as coloured by the following image. Grey is the body’s background, blue is the margin, and white/light is the column’s background.

<div class="container">
  <div class="row">
    <div class="col-4">
      the first column, text contents / stories
    </div>
    <div class="col-8">
      the second wider column where the map is going to live
    </div>
  </div>
</div>

I want the scroll to be interactive to the story/text, but not to the map. This means that the map needs to be fixed regardless of how much we scroll. This is where css comes in. Everything that ignores scroll requires the position to be fixed, and so I created the stay css class.

.stay {
    position: fixed;
    height: 100%;
    width: 100%
}

and stick it in the div where the map will live.

<div class="col-8">
      <!-- the second wider column where the map is going to live. -->
      <div class="stay" id="mapcontainer">
          <div id="map" ></div>
      </div>

</div>

This way, the map div will be fixed.

Maps and Javascript

as the map div is created, we can now import leaflet javascript library to make our maps. Leaflet provides the map interactivity tools that I need. It is a brilliant package; so simple but it works!

var map = L.map('map').setView([51.505, -0.09], 13);

L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

L.marker([51.5, -0.09]).addTo(map)
    .bindPopup('A pretty CSS3 popup.<br> Easily customizable.')
    .openPopup();

Sections and Scrolling

Just like Chow’s and Jim’s structure, the Arriving London page also consists of sections with id step .

<div class="col-4  full ">
  <section class="step ">
  my first section
  </section>
  <section class="step ">
  the second section 
  </section>
  <section class="step ">
  and so on
  </section>
  <section class="step ">
  ...
  </section>
</div>

Scroll Interactive

After the steps are defined, the document needs to track the scrolling activity from the user. The following code calls the trackPosition function every time the user scrolls.

d3.select(window)
    .on("scroll.scroller", trackPosition);

The trackPosition is as the following:


// sectionsArray are the div step (s) from the HTML that we previously defined
let activeSection;
let previousSection;
const trackPosition = ()=>{
    let pos = window.pageYOffset - 140;
    let sectionIndex = d3.bisect(sectionPositions, pos);
    sectionIndex = Math.min(sections.size() - 1, sectionIndex);
    activeIndex = sectionIndex

    if (sectionsArray[sectionIndex] !== activeSection){
        previousSection = activeSection
        activeSection = sectionsArray[sectionIndex] 

        d3.select(`#${activeSection}`)
            .transition()
            .style("opacity", "1");
        d3.select(`#${previousSection}`)
            .transition()
            .style('opacity', '0.2')
            ;

        positionMap(sectionIndex)
    } 
}

Please notice the positionMap function! This function is what makes the map changes.

// positionMap: changes the map based on the active step

const positionMap = (sectionIndex) =>{
    if (sectionIndex === 0){
        map.flyTo([51.505404,-0.118658], 9,) // zoom in to coords
        airportLayer.addTo(map) // leaflet layer
        elizaebthLine_st.remove() // leaflet layer
        popupairport() // popup the map
        attractions.remove() // leaflet layer
    }
    if (sectionIndex === 1){

        map.flyTo([51.509687,-0.115464], 13,) // zoom in to coords

        attractions.addTo(map) // leaflet layer
        attractions.eachLayer((l)=>{l.openTooltip()}) // open the tooltips

    // add another if and manually code the interactivity. Read the leaflet documentation.

}

Data Storage

In a schema-less data structure, which is basically an array containing JSON objects, we can add arbitrary properties. I demonstrate this in the data structure article.

Spatial Data Science: Data Structures

We can define a spatial data as minimal as this:

// minimum spatial data
const berlin = {
"city": "berlin", 
"country": "germany",
"location" : {
    "type" : "Point",
    "coordinates": [52.507650, 13.410298]
    }
}

But I confine to the GeoJSON spatial data specification, so the spatial data looks like this:

const attractions_geojson = {
    "type": "FeatureCollection",
    "features": [
      {
        // minimum spatial data but with geojson spec
        "type": "Feature",
        "properties": {
          "name": "big ben"
        },
        "geometry": {
          "coordinates": [
            -0.12466064329075266,
            51.50067738052945
          ],
          "type": "Point"
        }
      },
      {
        // minimum spatial data but with geojson spec
        "type": "Feature",
        "properties": {
          "name": "leicester square"
        },
        "geometry": {
          "coordinates": [
            -0.13047682089788282,
            51.51079955591317
          ],
          "type": "Point"
        }
      }
    ]
  }

The attractions_geojson.features containing our layers, in the code above, consists of 2 layers: Big Ben, and Leicester Square. The geojson.features is a list of all of the minimal spatial data.

You can view the spatial data in the variable.js when you load the demo link in your browser.

Conclusion

Using Javascript and HTML, we can make our data interactive. As a cartographer this means that I can make my maps interactive and responsive! The tricky bit is to store the spatial data as a javascript file instead of a conventional shapefile and geojson object.


Related Articles