Эта статья вдохновлена ​​прекрасным многосерийным учебником Картография из командной строки, написанным Майком Бостоком, и увлечением картами. Я настоятельно рекомендую прочитать все четыре статьи учебника. После того, как я просмотрел статьи и создал карту Техаса, я решил сделать карту Швейцарии, так как данные о ее границах легко найти в Интернете. Итак, эта статья о том, как я построил карту плотности населения Швейцарии.

Сначала установите необходимые пакеты npm (у вас уже должны быть установлены node и npm):

npm install -g csvtojson shapefile topojson ndjson-cli
npm install -g d3 d3-geo-projection d3-scale-chromatic

Если вы посмотрите на карту сверху, то увидите, что швейцарская граница пересекает большие озера в нескольких местах. Один из вариантов - оставить их незаполненными, но по умолчанию белый цвет означает необитаемые территории (а озера действительно таковыми), поэтому нам нужно, чтобы границы озер залили озера синим цветом, чтобы это было понятно. Также хорошо иметь границы кантонов, это улучшает карту. Нам нужно:

  1. Данные о плотности населения для каждого муниципалитета.
  2. Границы муниципальных образований;
  3. Границы озер;
  4. Границы кантонов;

Плотность населения определяет цвет муниципалитетов на карте, поэтому фактически у нас будет 3 «слоя» для рисования: границы муниципалитетов + цвет, который зависит от плотности населения; Границы озер + синий цвет, а границы кантонов + черта линии. Это все, что нам нужно.

Данные о плотности населения

Федеральное статистическое управление предоставляет разные статистические данные по Швейцарии. Воспользуйтесь ссылкой https://www.bfs.admin.ch/bfs/en/home/statistics/regional-statistics/regional-portraits-key-figures/communes.assetdetail.328146.html или, если ссылка устарело, попробуйте поискать по ключевым данным всех коммун. Вы можете скачать файл xls и преобразовать его в csv (я использовал LibreOffice для открытия файла и сохранил его как csv с именем Population-density.csv в ~ / projects / swiss-Population-dansity- карта / папка.

Верхние 9 строк файла csv содержат заголовки, которые нам не нужны, поэтому удалите их с помощью команды tail:

tail -n +10 population-density.csv \
  > population-density-no-headers.csv

Преобразуйте файл density-no-headers.csv в JSON:

csvtojson --noheader=true population-density-no-headers.csv \
  > population-density.json

Если нам нужно манипулировать и рисовать многоугольники, гораздо проще работать с форматом NDJSON (каждая строка файла является допустимым JSON):

ndjson-cat population-density.json \
  | ndjson-split \
  | ndjson-map 'd={id:parseInt(d.field1),residents:d.field3.replace(",", ""),density:d.field5.replace(",", "")}' \
  > population-density.ndjson

Также в приведенной выше команде мы упростили данные, b / c нам нужны только значения плотности и идентификаторы муниципалитетов. Теперь файл population-density.ndjson содержит простые строки JSON:

{"id":1,"residents":"1959","density":"248"}

Границы

Федеральное бюро топографии swisstopo предоставляет открытые данные об административных границах Швейцарии. Вы можете загрузить файл данных с сайта https://shop.swisstopo.admin.ch/en/products/landscape/boundaries3D (размер zip-файла 600 МБ) или, если URL-адрес устарел, попробуйте выполнить поиск по swiss sizes3D. Но, думаю, лучше использовать данные swiss-maps. Репозиторий содержит скрипты, позволяющие генерировать файлы TopoJSON для границ административных единиц и озер Швейцарии. Данные немного устарели (2015 г.), но дают нам все необходимое для рисования карты.

Сначала swisstopo предоставляет координаты в швейцарской системе координат, что на первый взгляд выглядит немного странно. [2765769.1224999987,1213634.7800000012] - это пример швейцарских координат. Он уже спроецирован (плоский), но координаты требуют некоторой нормализации, чтобы использовать их для рисования карты. swiss-maps делает это за нас.

Во-вторых, вы уже оптимизировали файлы TopoJSON. Вы можете прочитать о TopoJSON в Картография из командной строки, часть 3.

И последнее, но не менее важное: swiss-maps предоставляет границы озер, которых я не нашел в новом наборе данных boundaries3D (возможно, я просто пропустил это).

Пришло время использовать swiss-карты для создания файлов TopoJSON. Для скачивания swiss-карт необходим git:

cd ~/projects
git clone https://github.com/interactivethings/swiss-maps.git
cd swiss-maps

Я только что клонировал swiss-карты в папку ~ / projects /.

ПРИМЕЧАНИЕ: если вы попытаетесь работать внутри swiss-map для создания карты (без создания отдельной папки для вашего проекта), у вас будет конфликт версий модулей npm, b / c swiss-maps использует старые версии пакетов npm, установленных локально ( например, d3), но мы используем новую версию d3, установленную глобально. Поэтому я рекомендую вам создать отдельный рабочий каталог вне каталога swiss-maps.

Раздел swiss-maps Начало работы содержит всю информацию, необходимую для его установки и запуска. Swiss-maps полагается на GDAL, который необходимо установить. Чтобы сгенерировать файлы TopoJSON, запустите команду в каталоге swiss-maps:

make all

Если все в порядке, в папке ~ / projects / swiss-maps / topo / вы найдете файлы TopoJSON. Теперь сгенерируйте файлы NDJSON для границ внутри нашего рабочего каталога. Сгенерируйте файл GeoJSON для озер:

topo2geo lakes=- < ../swiss-maps/topo/ch-municipalities-lakes.json \
  > lakes.json

и для границ страны:

topo2geo country=- < ../swiss-maps/topo/ch-country-lakes.json \
  > country.json

Сгенерируйте NDJSON для границ страны:

ndjson-cat country.json | ndjson-split 'd.features' \
  | ndjson-map 'd.properties = {"stroke": "#000", "stroke-opacity": 0.3}, d' \
  > country.ndjson

и для границ озер:

ndjson-split 'd.features' < lakes.json \
  | ndjson-map 'd.properties.fill = "#6b9def", d' \
  > lakes.ndjson

Нарисуйте границы Швейцарии и озер:

cat country.ndjson lakes.ndjson \
  | geo2svg -n -w 960 -h 500 \
  > country-lakes.svg

Как видите, у нас небольшая проблема. Было бы неплохо иметь пересечение Швейцарии и озер, т.к. на карте Швейцарии нам не нужны французская часть Женевского озера, итальянская часть озера Маджоре и немецкая часть Боденского озера (Боденское озеро).

Быстрым и простым решением для меня было создание скрипта Python, который использует уже установленный GDAL для пересечения требуемых полигонов. Я использовал оболочку Python, которую немного сложно установить. Вы также можете поиграть с командной строкой GDAL, чтобы получить пересечение полигонов Швейцарии и озер без использования скрипта Python.

Скрипт Python (correct.py) очень прост, он считывает первую геометрию из STDIN и для каждой следующей геометрии, считанной из STDIN, возвращает ее пересечение с первой геометрией:

#!/usr/bin/env python
from osgeo import ogr
from osgeo import gdal
import sys
if __name__=='__main__':
    geoJsonMain = sys.stdin.readline()
    geometryMain = ogr.CreateGeometryFromJson(geoJsonMain)
    for geoJsonLine in sys.stdin:
        geometryLine = ogr.CreateGeometryFromJson(geoJsonLine)
        print geometryMain.Intersection(geometryLine).ExportToJson()

Если я запускаю скрипт более одного раза, мне нужна регистрация и проверка ввода, но это не так.

Скрипты Python работают с чистой геометрией, например:

{
  "type": "Polygon",
  "coordinates": [
    [
      [
        556.157854890176,
        151.31341313413128
      ],
      [
        554.1445563131838,
        148.27018270182697
      ]
    ]
  ]
}

но GeoJSON - это набор геометрических фигур с дополнительной информацией, например:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "id": 9040,
      "properties": {},
      "geometry": <Geometry goes here>
    },
    ...
  ]
}

Итак, нам нужно извлечь геометрию из GeoJSON, прежде чем мы будем использовать скрипт Python для озер. Мы можем использовать команду jq для извлечения геометрии озер:

cat lakes.json | jq -c '.features[] | [.geometry]' \
  | ndjson-split  > geometry-lakes.json

и кантри-геометрия:

cat country.json | jq -c '.features[0].geometry' \
  > geometry-country.json

Запустите скрипт python и добавьте синий цвет для заливки полигонов озер:

cat geometry-country.json geometry-lakes.json \
  | ./intersect.py \
  | ndjson-map '{type: "Feature", properties: {fill: "#6b9def"}, geometry: d}' \
  >  lakes-intersected.ndjson

Снова нарисуйте озера:

cat \
  <(cat lakes-intersected.ndjson) \
  <(ndjson-split 'd.features' < country.json) \
  | geo2svg -n -w 960 -h 500 \
  > country-lakes-intersected.svg

Хорошо. Теперь сгенерируйте файлы для границ кантонов:

topo2geo cantons=- < ../swiss-maps/topo/ch-cantons-lakes.json \
  | ndjson-split 'd.features' \
  | ndjson-map 'd.properties = {"stroke": "#000", "stroke-opacity": 0.3}, d' \
  > cantons.ndjson

и муниципалитеты:

topo2geo municipalities=- \
  < ../swiss-maps/topo/ch-municipalities-lakes.json \
  | ndjson-split 'd.features' \
  > municipalities.ndjson

Теперь мы используем ndjson-join для объединения данных из population-density.ndjson и муниципалитетов.ndjson, используя идентификаторы муниципалитетов, представленные в обоих файлах, и назначаем цвет каждому муниципалитету. многоугольник (используйте модуль d3 d3-scale-chromatic), зависит от значения плотности населения:

ndjson-join 'd.id' municipalities.ndjson population-density.ndjson \
  | ndjson-map -r d3 -r d3=d3-scale-chromatic 'z = d3.scaleThreshold().domain([1, 10, 50, 200, 500, 1000, 2000, 4000]).range(d3.schemeOrRd[9]),d[0].properties = {    fill: z(d[1].density)}, d[0]' \
  > merged-data.ndjson

Обновление от 30 августа 2019 г.: если у вас возникла проблема с предыдущей командой, попробуйте слегка измененную версию (используется -r d3-scale-chromatic вместо -r d3 = d3-scale-chromatic):

ndjson-join 'd.id' municipalities.ndjson population-density.ndjson \
  | ndjson-map -r d3 -r d3-scale-chromatic 'z = d3.scaleThreshold().domain([1, 10, 50, 200, 500, 1000, 2000, 4000]).range(d3.schemeOrRd[9]),d[0].properties = {    fill: z(d[1].density)}, d[0]' \
  > merged-data.ndjson

Наконец, просто нарисуйте карту, вы можете увидеть сверху:

cat merged-data.ndjson lakes-intersected.ndjson \
  cantons.ndjson country.ndjson \
  | geo2svg --stroke none -n -w 960 -h 500 \
  > swiss-population-density.svg

Мне было весело создавать карту плотности населения Швейцарии в командной строке (почти), поэтому я решил поделиться ею здесь.