- PVSM.RU - https://www.pvsm.ru -
Приветствую вас, читатели! Сегодня я расскажу вам как сделать интерактивную d3js.org [1], о возможностях этой JavaScript библиотеки в общем, а также придётся немного разобраться в том как и где лучше хранить геоинформацию для веба. В финале мы получим следующее:

Начать сие увлекательное путешествие можно под катом.
В принципе, этот раздел можно пропустить, если не интересно, ссылка на нужный файл в самом конце раздела. Кому интересно, разбираемся дальше. Что такое карта, по сути это информация о геометрии некоего объекта с привязкой к координатам. В shapefile'ы [2]. Мы будем рисовать карту России, скорее всего поиск приведёт вас туда же куда и меня, а именно на GIS-Lab [3]. Я выбрал проекцию Albers-Siberia. Скачиваем. Первым делом нам нужно преобразовать наш shapefile по стандарту WGS 84 [4] (оно же EPSG:4326). Для этого необходимо создать файл проекции [5], например Albers_Siberia.prj со следующим содержимым :
+proj=aea +lat_1=52 +lat_2=64 +lat_0=0 +lon_0=105 +x_0=18500000 +y_0=0 +ellps=krass +units=m +towgs84=28,-130,-95,0,0,0,0 +no_defs
Затем при помощи GDAL [6], а точнее одной из его библиотек OGR [7] выполним преобразование. Я для этих нужд скачал Quantum GIS [8], который уже содержит всё что нужно и даже больше. После установки у вас появится несколько ярлыков, ищем среди них OSGeo4W, жмакаем, переходим в каталог с нашими файлами и вводим команду следующего вида:
ogr2ogr -f 'ESRI Shapefile' -s_srs Albers_Siberia.prj -t_srs EPSG:4326 input-fixed.shp input.shp
Таким образом мы получили нужный нам shapefile, хотя как нужный, для веба он нам совсем не подходит, поэтому теперь сгенерируем GeoJSON [9] файл на основе наших данных. Затем из GeoJSON'а сгенерируем TopoJSON [10], который-то и нужен для нашей картограммы. Такие вот дела, но вы не расстраивайтесь, GeoJSON- штука тож полезная, авось пригодится. Итак, идём опять в консоль и пишем примерно следующее:
ogr2ogr -f GeoJSON output.json input.shp
Получаем наш GeoJSON файл, открываем его и видим сюрприз от GIS-Lab.
Теперь перейдём к генерации TopoJSON, это позволит нам уменьшить размер файла. Вообще TopoJSON является оптимизацией GeoJSON'а в плане топологий, он убирает избыточную информацию, например убирает дублирование общих границ у соседних регионов. Но мы можем уменьшить размер файла ещё больше, упростив геометрию. Итак, приступим! Запускаем командную строку Node.js [12] (он нужен для имплементации TopoJSON [13]) и пишем следующее:
topojson -o output_topo.json -p -s 1e-7 -- name=input_geo.json
Здесь параметр -p отвечает за сохранение feature properties, а -s 1e-7 за упрощение геометрии, 1e-7 это порог в стерадианах [14] чем меньше, тем точнее геометрия: 1e-3 это Швейцария относительно карты мира, а 1e-9 футбольное поле. Для чего это может быть нужно — если вы захотите сделать возможность зума на вашей карте. Разделитель -- это просто разделитель (ваш К.О.) выходного и входного файлов, а префикс russia задаёт имя объекта, если его не указывать, то в качестве имени будет использоваться имя входного файла, что не всегда удобно (может быть громоздким). В полученном файле я заменил названия регионов на коды в соответствии с ISO 3166-2:RU [15]. Всё. Файл можно взять на GitHub [16].
Карту отрисовывать будем вообще или как?! Теперь у нас есть всё, что необходимо для отрисовки карты средствами d3.js. Скопируйте следующий шаблон и приступим:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Accidents on the Road - Choropleth</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="http://d3js.org/queue.v1.min.js"></script>
<script type="text/javascript" src="http://d3js.org/topojson.v0.min.js"></script>
<!-- <script type="text/javascript" src="http://d3js.org/topojson.v1.min.js"></script> -->
</head>
<style>
your awesome CSS
</style>
<body>
<h1>Cool Header</h1>
<script type="text/javascript">
Your awesome d3.js code
</script>
</body>
</html>
Сначала зададим размеры нашей SVG карты.
var width = 960,
height = 500;
Зададим домен цветов для картограммы, домен для легенды и подписи к легенде.
var color_domain = [50, 150, 350, 750, 1500]
var ext_color_domain = [0, 50, 150, 350, 750, 1500]
var legend_labels = ["< 50", "50+", "150+", "350+", "750+", "> 1500"]
var color = d3.scale.threshold()
.domain(color_domain)
.range(["#adfcad", "#ffcb40", "#ffba00", "#ff7d73", "#ff4e40", "#ff1300"]);
Добавим в документ элемент и класс tooltip.
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
Добавим SVG с атрибутами, задающими размер.
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
Зададим параметры проецирования (вспоминаем/смотрим Albers_Siberia.prj [17] из начала статьи):
var projection = d3.geo.albers()
.rotate([-105, 0])
.center([-10, 65])
.parallels([52, 64])
.scale(700)
.translate([width / 2, height / 2]);
var path = d3.geo.path().projection(projection);
Читаем данные.
queue()
.defer(d3.json, "/d/5685937/russia_1e-7sr.json")
.defer(d3.csv, "Accidents.csv")
.await(ready);
Начинаем отрисовку. Создаем объекты для пар код региона: кол-во смертей и код региона: название региона.
function ready(error, map, data) {
var rateById = {};
var nameById = {};
data.forEach(function(d) {
rateById[d.RegionCode] = +d.Deaths;
nameById[d.RegionCode] = d.RegionName;
});
Отрисовка и раскраска картограммы.
svg.append("g")
.attr("class", "region")
.selectAll("path")
.data(topojson.object(map, map.objects.russia).geometries)
//.data(topojson.feature(map, map.objects.russia).features) <-- in case topojson.v1.js
.enter().append("path")
.attr("d", path)
.style("fill", function(d) {
return color(rateById[d.properties.region]);
})
.style("opacity", 0.8)
Обрабатываем события: меняем яркость региона (для подсветки) и выводим в tooltip'е название региона и точное численное значение.
.on("mouseover", function(d) {
d3.select(this).transition().duration(300).style("opacity", 1);
div.transition().duration(300)
.style("opacity", 1)
div.text(nameById[d.properties.region] + " : " + rateById[d.properties.region])
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY -30) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition().duration(300)
.style("opacity", 0.8);
div.transition().duration(300)
.style("opacity", 0);
})
Теперь хотелось бы научиться добавлять чего-нибудь на эту самую карту, я решил добавить города-милионники России, для этого нам собственно нужен сам город и его координаты (широта и долгота в десятичных градусах), к сожалению найти геокодер вроде этого gpsvisualizer.com/geocoder [18], чтобы он понимал русский язык- я не смог (может кто знает?), а лезть в API Яндекс.Карт не хотелось, тем более что список маленький. Хорошо бы они сами колдунчик такой сделали, ну да ладно, отвлекся я. В итоге получили список следующего вида:
City lat lon
Москва 55.7522200 37.6155600
Санкт-Петербург 59.8944400 30.2641700
Ну собственно и добавим их, группой: точка-подпись.
d3.tsv("cities.tsv", function(error, data) {
var city = svg.selectAll("g.city")
.data(data)
.enter()
.append("g")
.attr("class", "city")
.attr("transform", function(d) { return "translate(" + projection([d.lon, d.lat]) + ")"; });
city.append("circle")
.attr("r", 3)
.style("fill", "lime")
.style("opacity", 0.75);
city.append("text")
.attr("x", 5)
.text(function(d) { return d.City; });
});
};
Тут хотелось бы добавить, что вместо точек можно добавить, например, круговую/кольцевую диаграмму, увеличив тем самым информационную нагрузку на нашу картограмму. Вообще возможностей масса, всё ограничивается вашими задачами, воображением и целесообразностью с точки зрения UI/UX.
Ну и под конец добавим легенду нашей картограмме:
var legend = svg.selectAll("g.legend")
.data(ext_color_domain)
.enter().append("g")
.attr("class", "legend");
var ls_w = 20, ls_h = 20;
legend.append("rect")
.attr("x", 20)
.attr("y", function(d, i){ return height - (i*ls_h) - 2*ls_h;})
.attr("width", ls_w)
.attr("height", ls_h)
.style("fill", function(d, i) { return color(d); })
.style("opacity", 0.8);
legend.append("text")
.attr("x", 50)
.attr("y", function(d, i){ return height - (i*ls_h) - ls_h - 4;})
.text(function(d, i){ return legend_labels[i]; });
Вот в принципе и всё, я старался показать основные моменты на простом примере, надеюсь что мне это удалось. Код конечно не идеален (идеального ничего не бывает), но цель, повторюсь, была сделать его понятным, а не супер универсальным/эффективным. Исходники можно найти на GitHub [19], а пощупать результат можно через сервис bl.ocks.org [20]. Да, CSS мы тут не рассматривали, но там всё тривиально.
Ну вот мы и создали простенькую картограмму, без возможности приближения, сложной анимации и прочих наворотов, но при желании добавить их сюда не составит большого труда. Вообще данная библиотека имеет широчайшие возможности для визуализации всего и вся: графики, диаграммы, картограммы, деревья, графы, чарты, тепловые карты…. Такой вот DataViz комбайн, всё что нужно можно найти через сайт d3js.org [1]. Mike Bostock активно развивает проект, почти каждый день выкладывает новые примеры (один минус, почти все без комментариев), на stackoverflow [21] тоже много чего и отвечают там оперативно, в том числе и сам Майк. Так что дерзайте, имхо эта библиотека, являющаяся по сути основным инструментом визуализации за бугром, у нас несправедливо обделена вниманием. Ну и я, если это будет интересно хабросообществу, буду периодически разбирать интересные примеры. Собственно всё, комментарии, вопросы и предложения приветствуются.
P.S. Чуть не забыл самое главное, будьте осторожнее на дорогах, особенно если вы не с Чукотки, уродов всяких у нас хватает и масса видео с регистраторов тому подтверждение! А вообще это печально, почти 28 тысяч смертей за год, ужас просто (данные брал с офф. сайта ГИБДД).
Автор: KoGor
Источник [22]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/veb-razrabotka/35693
Ссылки в тексте:
[1] d3js.org: http://d3js.org
[2] shapefile'ы: http://ru.wikipedia.org/wiki/Shapefile
[3] GIS-Lab: http://gis-lab.info/qa/rusbounds-rosreestr.html
[4] WGS 84: http://ru.wikipedia.org/wiki/WGS_84
[5] проекции: http://gis-lab.info/qa/gis-lab-projections.html
[6] GDAL: http://www.gdal.org
[7] OGR: http://www.gdal.org/ogr2ogr.html
[8] Quantum GIS: http://www.qgis.org
[9] GeoJSON: http://geojson.org/
[10] TopoJSON: https://github.com/mbostock/topojson/wiki
[11] шифровальную книгу: http://habrahabr.ru/post/147843/
[12] Node.js: http://nodejs.org/
[13] TopoJSON: https://npmjs.org/package/topojson
[14] стерадианах: http://en.wikipedia.org/wiki/Steradian
[15] ISO 3166-2:RU: http://ru.wikipedia.org/wiki/ISO_3166-2:RU
[16] GitHub: https://github.com/KoGor/Maps.GeoInfo
[17] Albers_Siberia.prj: #prj_param
[18] gpsvisualizer.com/geocoder: http://www.gpsvisualizer.com/geocoder/
[19] GitHub: https://gist.github.com/KoGor/5685876
[20] bl.ocks.org: http://bl.ocks.org/KoGor/5685876
[21] stackoverflow: http://stackoverflow.com/questions/tagged/d3.js
[22] Источник: http://habrahabr.ru/post/181766/
Нажмите здесь для печати.