Введение
Вам когда-нибудь нужно было отображать крупные массивы данных с привязкой к карте? Мне на работе понадобилось отображать заказы сгруппированные по широте и долготе. И не просто статической таблицей, а динамической, с разной детализацией для разного приближения карты.
К сожалению (или к счастью?), готовых решений я не нашёл. Google Карты позволяют накладывать маркеры и фигуры на карты, но эти способы представляют слишком мало информации. С Яндекс картами оказалось не лучше. Но Карты Гугл имеют механизм пользовательских наложений с HTML-содержанием. И для инкапсуляции этой работы с картами и наложениями я создал JavaScript библиотеку GMapsTable. Возможно, кому-нибудь она окажется интересной или полезной. Рабочий пример.
Чтобы не возникло путаницы, параметр zoom будем называть приближением карты, а scale — масштабом. Первый относится к Google Maps API, а второй к описываемой библиотеке.
Задача в целом
Итак, что у нас есть? Какой-нибудь источник данных (например, сервер с базой данных, обрабатывающий и посылающий данные в формате JSON) и веб-страничка с JavaScript, которая запрашивает данные и визуализирует их на Картах Гугл.
Данные имеют аккумулятивную природу (в моём случае каждой области можно поставить в соответствие: число заказов, клиентов и среднюю сумму). Поэтому данные могут и должны отображаться с разной детализацией для разных приближений.
Основное содержание HTML страницы для GMapsTable:
..в <head>:
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_KEY"></script>
<script src="http://www.aivanf.com/static/gmt/gmapstable.js"></script>
..в <body>:
<div id="map"></div>
GMapsTable позволяет абстрагироваться от взаимодействия с GoogleMaps API. Вам нужно лишь предоставить подходящий объект с данными. Время перейти к JavaScript'y! Чтобы использовать GMapsTable, нужно получить объект DataContainer для Вашего div'a карты:
// Аргумент: ID div'а
// и словарь параметров GoogleMaps,
// это не обязательно
var container = new DataContainer("map", {
zoom: 9,
center: {lat: 55.7558, lng: 37.6173},
mapTypeId: 'roadmap'
});
Затем нужно передать две функции:
container.dataLoader = function (scale, borders) {
... вызвать container.processData(some_data);
}
container.scaler = function (zoom) {
... return какое-нибудь число;
}
Но что именно писать внутри функций?.. Для начала разберёмся, как работает GMapsTable.
Data для DataContainer
DataContainer
занимается отображением Ваших данных и заботится о том, когда оно должно быть обновлено. В самом начале и когда изменяются приближение и границы "камеры", он пробует использовать сохранённые данные, а если их нет, то вызывает функцию dataLoader
. Вам нужно сгенерировать объект с данными и передать его функции DataContainer.processData. Структура объекта должна быть такая:
data: {
minLat: float,
difLat: float,
minLon: float,
difLon: float,
scale: int,
table: [
[value, value, ...],
[value, value, ...],
...
],
tocache: boolean
}
Значением (value
) может быть число, строка или любой объект, если вы укажите собственную функцию форматирования ячейки таблицы. Масштаб (sale
) это целое число, говорящее, на сколько частей должны делиться единицы широты и долготы. Параметр tocache
указывает, должны ли данные для текущего масштаба быть сохранены и более не запрашиваться.
data: {
minLat: 55.0,
difLat: 2.0,
minLon: 37.0,
difLon: 1.0,
scale: 2,
table: [
[1, 3, 0, 1],
[0, 1, 2, 0]
],
tocache: true
}
Здесь данные покрывают область от 55.0, 37.0 до 57.0, 38.0 и делят каждую единицу широты и долготы на 2 части (получается, одна клетка широты-долготы делится на 4 части). Также здесь указано, что для данного масштаба это полные данные, и они должны быть сохранены для использования в дальнейшем.
Перевод приближения в масштаб
Приближение (zoom
) это параметр Google Maps API, целое число между 1 (карта мира) и 22 (улица). Запрашивать и хранить данные для каждой единицы приближения неудобно и нецелесообразно, поэтому GMapsTable переводит их в масштаб (scale
) — число, указывающее, на сколько частей нужно делить единицу широты и долготы.
Сохранение данных
Чтобы отображение при изменении масштаба было моментальным, GMapsTable хранит наборы данных для некоторых (либо всех) масштабов. Например, у меня была база данных с координатами почти со всей России — около 42 тысяч ячеек для масштаба 10 (500 КБ, довольно легко хранится и обрабатывается у меня в десктопном браузере) и 17 миллионов для масштаба 200 (несколько МБ, вызывает значительные подвисания). Поэтому сервер оценивает число ячеек всех данных, и если их немного, отправляет данные из всей БД, иначе только для запрошенного региона. Получается такой алгоритм:
Границы (bounds
) — это объект JavaScript с полями minlat, maxlat, minlon, maxlon
— текущими границами Google Maps и хорошим отступом про запас.
В Вашей реализации dataLoader
Вы можете смело игнорировать аргументы, если нет нужды использовать разную детализацию для разных масштабов или если Ваши данные не покрывают такой большой регион. Просто передайте данные и их границы по широте и долготе и scale
, на сколько разбиваете единицы широты-долготы. Но для полноты картины я предлагаю такое поведение функции dataLoader
(или сервера, к которому она обращается):
Список всех параметров
Вы можете указать такие параметры для DataContainer:
1) scaler(zoom)
— переводит приближение из GoogleMaps в масштаб для GMapsTable. Оба целые числа.
2) dataLoader(scale, borders)
— вызывается, когда нужны новые данные. Должен передать объект данных в DataContainer.processData(data)
.
Параметр borders
это объект JavaScript с полями minlat, maxlat, minlon, maxlon
— текущими границами Google Maps и хорошим отступом про запас.
3) tableBeforeInit(map, table, data)
— вызывается перед тем, как таблица начинает заполняться ячейками. Аргумент map
это объект Google Maps, table
это HTML элемент таблицы, а data
— предоставленный Вами объект данных для текущего масштаба.
4) cellFormatter(td, val)
— вызывается для заполнения ячейки. td
это HTML element, ячейка таблицы. val
это данные из Вашего объекта данных.
5) boundsChangedListener(zoom)
— вызывается, когда изменяются границы Google Maps.
6) minZoomLevel
, maxZoomLevel
— переменные для минимального и максимального приближения карты. Целые числа между 1 (карта мира) и 22 (улица).
Для успешной работы DataContainer необходимы только первые две функции.
Пример и исходники
Полный и хорошо прокомментированный пример использования: HTML-страничка и JS-код.
А также есть GMapsTable в GitHub.
Автор: AivanF