Интерактивное представление пространственных данных с помощью прокрутки

Рассказывать истории и идеи с помощью интерактивных карт

Об этой статье

Эта статья о карте-истории, которую я создал:

…и эта статья – рассказ о том, как я его построил.

Введение

Продукт картографа — это карты. Мы работаем с шейп-файлами (которые являются наследием хранения пространственных данных), базами геоданных, geojson и многими формами пространственных данных и используем ГИС для создания карт из них. Эти карты, однако, часто представляют собой документы в формате JPEG, PNG или PDF, которые не являются интерактивными. В большинстве случаев да, это работает, но мне нравится рассказывать и мне рассказывают истории, когда мне не нужно делать большую часть работы. С меньшими усилиями, как я могу рассказывать истории?

Затем я наткнулся на статью Кутберта Чоу о D3.js. D3.js — это библиотека javascript для управления элементами и svg в DOM (объектная модель документа). Люди используют D3.js для представления данных, в которых Чоу провел великолепную демонстрацию, расширив статью Джима Валландингема. Посмотрите на его следующую статью.



И тут до меня дошло: мы можем сделать это и с картами! Идея прокрутки и интерактивного представления данных возможна с использованием javascript. В этой статье рассказывается о том, как я это сделал.

Вы можете найти живую демонстрацию здесь:



Стеки

Все ресурсы данных и карт имеют открытый исходный код (openstreetmap). Ниже приведены стеки, которые я использовал

  • jquery и d3.js: работа с документами
  • leaflet.js: интерактивная веб-карта

Общая идея

Я пишу о науке о пространственных данных и написал статью о представлении пространственных данных в следующей статье. Эта статья является демонстрацией.



По сути, идея того, как это работает, такова:

  • Использование Python для получения и анализа пространственных данных.
  • Использование HTML для разметки документов, CSS для придания красоты; представление содержания.
  • Использование Javacript для интерактивности. В этом случае меняем карты при прокрутке.

Дополнительно (не в этой статье/проекте) мы можем что-то сделать с источниками данных:

  • Хранение данных на сервере базы данных postgresql + postgis.
  • Apache Airflow для организации/автоматического сбора данных и заполнения сервера базы данных.
  • Использование геосервера в качестве внутреннего картографического сервера.

Возможно, для будущих проектов.

Как это работает

Подробности рассказывать не буду, а то статья получится очень длинной. То, что я объясню, — это идея высокого уровня, которую разработчики должны… разрабатывать.

Большая часть кода взята из статьи Джима о скроллере javascript. Демо на основе этого кода доступно здесь.



Но в этой статье я обсуждаю свою демонстрацию. Я разобью его на основе этих компонентов:

  • Макет
  • Разделы и прокрутка
  • Хранилище данных

Макет — исправление карты

Вёрстка основана на модуле Bootstrap 5 css. Это очень распространенный модуль, который быстро украшает HTML. Он предоставляет необходимые минимальные компоненты пользовательского интерфейса.

В частности, я сделал 2 столбца, как показано на следующем изображении. Серый — это фон тела, синий — поля, а белый/светлый — фон столбца.

<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>

Я хочу, чтобы прокрутка была интерактивной с историей/текстом, но не с картой. Это означает, что карту нужно фиксировать независимо от того, сколько мы прокручиваем. Здесь на помощь приходит css. Все, что игнорирует прокрутку, требует фиксированной позиции, поэтому я создал класс stay css.

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

и вставьте его в div, где будет жить карта.

<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>

Таким образом, карта div будет исправлена.

Карты и Javascript

поскольку карта div создана, теперь мы можем импортировать leaflet библиотеку javascript для создания наших карт. Leaflet предоставляет инструменты интерактивности карты, которые мне нужны. Это блестящий пакет; так просто, но это работает!

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

L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <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();

Разделы и прокрутка

Как и структура Чоу и Джима, страница «Прибытие в Лондон» также состоит из разделов с идентификатором 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>

Интерактивная прокрутка

После того, как шаги определены, документ должен отслеживать активность пользователя при прокрутке. Следующий код вызывает функцию trackPosition каждый раз, когда пользователь прокручивает страницу.

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

trackPosition выглядит следующим образом:

// 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)
    } 
}

Обратите внимание на функцию positionMap! Эта функция заставляет карту изменяться.

// 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.
    
}

Хранилище данных

В структуру данных без схемы, которая в основном представляет собой массив, содержащий объекты JSON, мы можем добавлять произвольные свойства. Я демонстрирую это в статье о структуре данных.



Мы можем определить пространственные данные как минимум следующим образом:

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

Но я ограничиваюсь спецификацией пространственных данных GeoJSON, поэтому пространственные данные выглядят так:

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"
        }
      }
    ]
  }

attractions_geojson.features, содержащий наши слои, в приведенном выше коде состоит из 2 слоев: Биг Бен и Лестер-сквер. geojson.features — это список всех минимальных пространственных данных.

Вы можете просмотреть пространственные данные в variable.js при загрузке демонстрационной ссылки в браузере.

Заключение

Используя Javascript и HTML, мы можем сделать наши данные интерактивными. Как картограф, это означает, что я могу сделать свои карты интерактивными и отзывчивыми! Хитрость заключается в том, чтобы хранить пространственные данные в виде файла javascript вместо обычного шейп-файла и объекта geojson.