Introduction
Have you ever needed to place some sort of geographic data on a map? For example, in my job, I develop analytical web services and I had to display orders grouped by latitude and longitude. And not a static table, but dynamic one, which changes its detailization with the map zoom!
Unfortunately, I found no existing solution for this task. Google Maps allow to add markers and shapes to the map, but this is not appropriate, too little information is shown. However, Google Maps allow to create custom overlays. Thus, I created GMapsTable
for encapsulating this stuff. A live example is here.
Solving the Problem
So, what do we have? A server with a database, that processes and sends data in JSON; and a client with JavaScript, that sends queries and handles the results putting the data on Google Maps.
The data has accumulative nature (in my case, each region may have: orders count, clients count, and mean sum of orders). The data can and must be presented with different detailization for different map scales. And this is done on server-side only, client does not change the data.
Here is the main content of an HTML page for GMapsTable
:
..in <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>
..in <body>:
<div id="map"></div>
GMapsTable
allows you to abstract from direct interaction with GoogleMaps
API. All you need is to provide an appropriate data object. Time to dive into JavaScript! To use GMapsTable
, you need to obtain a DataContainer
object for your map’s div
element:
var container = new DataContainer("map", {
zoom: 9,
center: {lat: 55.7558, lng: 37.6173},
mapTypeId: 'roadmap'
});
Then you have to specify two functions:
container.dataLoader = function (scale, borders) {
... call container.processData(some_data)
}
container.scaler = function getScale(zoom) {
... return some number
}
But what to write inside?... Let’s begin with how GMapsTable
works.
Data for DataContainer
DataContainer
handles your data visualisation process and worries if it must be updated. At the start and when zoom or bounds are changed significantly, it calls dataLoader
function. There you should generate a data object and pass it to DataContainer.processData
function. Its structure is as follows:
data: {
minLat: float,
difLat: float,
minLon: float,
difLon: float,
scale: int,
table: [
[value, value, ...],
[value, value, ...],
...
],
tocache: boolean
}
A value can be a number, a string or other object if you specify custom cell formatter. Scale
is a number of parts in each lat
/lon
unit. The "tocache
" property says if the values for this scale should be saved and no longer requested. To clarify, have a look at an example:
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
}
Here, the data covers region from 55.0, 37.0 to 57.0, 38.0 and splits every lat
/lon
into two parts. Also the values will be saved for this scale value.
Zoom to Scale
The zoom is the variable of Google Maps API, ranged between 1 (world map) and 22 (street view). It’s expensive and helpless to request and store values for every zoom value, so, GMapsTable
translates it to scale value — number of parts to divide latitude and longitude units.
Caching the Data
To provide flexible and immediate table display, GMapsTable
can store dataset
s for some scales. For example, I had a table on my database with coordinates from all of Russia — about 42 thousand cells for scale 10 (500 KB, can be easily stored and processed on a desktop browser) and 17 million for scale 200 (several MB, causes significant freezing). Hereby, the server estimates cells count and to a certain extent, starts cutting the data. The algorithm:
The bounds
argument is a JavaScript object with properties minlat
, maxlat
, minlon
, maxlon
— current bounds of Google Maps view with big padding on each side.
In your dataLoader
realisation, you can ignore arguments if you don’t need scaling the detailization or if your data does not cover huge region — just indicate preferred min
/max
/lat
/lon
and scale in your data response. But I suggest the full behaviour of dataLoader
:
List of Properties
You may specify the following properties of DataContainer
:
scaler(zoom)
— translates GoogleMaps
zoom
to GMapsTable scale
. Both are integer numbers.
dataLoader(scale, borders)
— is called when DataContainer
needs new data. Must call DataContainer.processData(data)
.
borders
is a JavaScript object with properties minlat
, maxlat
, minlon
, maxlon
— current bounds of Google Maps view with big padding on each side.
tableBeforeInit(map, table, data)
— is called when table
element was created but not filled with rows and cells. map
is a Google Maps object, table
is an HTML element, and data
is your data object for current scale.
cellFormatter(td, val)
— is called when table
is filling with rows and cells. td
is an HTML element, a cell of the table. val
is a value from provided data object.
boundsChangedListener(zoom)
— is called when Google Maps bounds are changed.
minZoomLevel
, maxZoomLevel
— minimal and maximal zoom of Google Maps. Integer numbers ranged between 1 (world map) and 22 (street view).
Only the 1st and 2nd are necessary for DataContainer
successful operation.
Example and Sources
You can find full and well-commented example of GMapsTable
usage here:
SaveSaveSave
SaveSave