Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Heatmap based on average weights and not on the number of data points

I am making a heatmap with Google API v3. I'll give an example. Lets consider earthquake magnitudes. I assign weights to each point to specify their magnitude. However google considers the density of points when you zoom out. The more points are in a place, the redder it gets. For example if two earthqueaks happened within miles of each other, one with magnitude 3 and another with magnitude 8, the first one should be green/blue and the 2nd one would be red. But once you zoom out and the two points get closer in the map, google maps considers the number of points instead of the weights and as a result, it appears read. I want it to be the average i.e. (3+8)/2=5.5...whatever color that represents. Is this possible?

like image 441
user2000796 Avatar asked Jan 22 '13 15:01

user2000796


People also ask

How is a heatmap calculated?

You can think of a heat map as a data-driven “paint by numbers” canvas overlaid on top of an image. In short, an image is divided into a grid and within each square, the heat map shows the relative intensity of values captured by your eye tracker by assigning each value a color representation.

When should you use a heatmap?

Generally speaking, heat maps are best used when something has changed either in your customer base or your website and you want to understand how that affects useability. For example, if you move a website element above the fold or change its color, does that increase clicks and conversions?

What is Density Heatmap?

One common map type for this is a density map, also called a heatmap. Tableau creates density maps by grouping overlaying marks and color-coding them based on the number of marks in the group. Density maps help you identify locations with greater or fewer numbers of data points.

What does a heatmap show you?

A heat map shows a color-coded overlay of mouse (and tap) movement on a single website page. The 'popularity' of page elements is displayed using a color scale from red (the most popular parts of the page) to blue (the least-used parts of a page).


2 Answers

There is a somewhat decent workaround if you are like me and don't have the processing time or power to generate an overlay and you can't modify any existing libraries to your liking.

I used the google maps heatmap library and set maxIntensity and dissipating to false. Setting the maxIntensity to a value of your choosing will solve the problem of your heatmap-points getting colored in relation to eachother instead of to 0 or a set value. Setting dissipating to false will disable the automatic radius settings that happen when you change zoom levels.

Next I made an event for every time the zoom level changed and in that event I set the radius to a value that seemed to represent my data in the most accurate way for that zoom level.

Now to get rid of the problem where datapoints on the map blend and get added together into a big red blob, I decided to make a seperate grid on my map for every zoom level I want to use. I average all the values that are boxed inside the same grid point and make sure that the grid is large enough to keep heatmap points from overlapping, but small enough to not look like a bunch of circles. (I found that the grid should be about 0.4 times the size of the heatpoint radius on the map for smooth looks).

The radius of the heatmap point is set by google in pixels. I didn't know how to convert pixels to Lat/Long so I just measured it by drawing lines past a circle with a certain radius and measured the distance between these lines. That conversion method will work pretty well if you aren't planning on mapping much more than a small country.

Performance wise this isn't as bad as I thought it would be. I'm loading about 2300 points and the map loads as fast as it did before I made a grid for every zoom level, and you don't actually see the data points being refreshed as you change zoom levels.

Here are some pieces of code for all of the above:

Map settings:

map.heatmap.set('maxIntensity', 12000);
map.heatmap.set('dissipating', false);

Change grid and radius per zoom level:

map._on({
    obj: map.gMap,
    event: "zoom_changed",
    callback: function(){
        var zoomLevel = map.zoom();
        switch(zoomLevel){
            case 7:
                map.heatmap.setData(gridData[zoomLevel]);
                map.heatmap.set('radius', 0.04);
                break;
            case 8:
                map.heatmap.setData(gridData[zoomLevel]);
                map.heatmap.set('radius', 0.03);
                break;
            case 9:
                map.heatmap.setData(gridData[zoomLevel]);
                map.heatmap.set('radius', 0.02);
                break;
            case 10:
                map.heatmap.setData(gridData[zoomLevel]);
                map.heatmap.set('radius', 0.01);
                break;
            case 11:
                map.heatmap.setData(gridData[zoomLevel]);
                map.heatmap.set('radius', 0.005);
                break;
            case 12:
                map.heatmap.setData(gridData[zoomLevel]);
                map.heatmap.set('radius', 0.0025);
                break;
            case 13:
                map.heatmap.setData(gridData[zoomLevel]);
                map.heatmap.set('radius', 0.00225);
                break;
            default:
                map.heatmap.setData(gridData[zoomLevel]);
                map.heatmap.set('radius', 0.000625);
        }
    }
});

My grids are generated in PHP which will probably look different for everyone, but just as an example, here's the function I use:

function getHeatGrid($gridSize){
$mapGrid = false;
$mapData = false;
$radius = $gridSize * 2.8 * 0.3; //grid size is multiplied by 2.8 to convert from the heat map radius to lat/long values(works for my lat/long, maybe not yours). * 0.3 is arbitrary to avoid seeing the grid on the map.
$string = file_get_contents("mapData.json");
$json_a = json_decode($string, true);

forEach($json_a as $key => $value){
    $row = intval(round(($value['center_longitude'] / $radius)));
    $column = intval(round(($value['center_latitude'] / $radius)/68*111)); //around 52.0;5.0 latitude needs to be scaled to make a square grid with the used longitude grid size
    if(isset($mapGrid[$row][$column])){
        $mapGrid[$row][$column] = round(($value['solarValue'] + $mapGrid[$row][$column]) / 2);
    } else {
        $mapGrid[$row][$column] = $value['solarValue'];
    }
}

forEach($mapGrid as $long => $array){
    forEach($array as $lat => $weight){
        $mapData[] = array(
            "center_longitude" => $long * $radius,
            "center_latitude" => ($lat * $radius)/111*68,
            "solarValue" => $weight
        );
    }
}
return $mapData;
}

Unfortunately I can't display the map right now as it is currently kept private for clients of the company that I work at, but if it becomes available publically I will add a link so you can see how well this method works.

Hope this helps someone.

Lukas

like image 138
Lukas Avatar answered Oct 17 '22 17:10

Lukas


You can do it by overlaying a heatmap image on top of the map. There's a very good open source example at https://github.com/jeffkaufman/apartment_prices

like image 28
Johnny Avatar answered Oct 17 '22 19:10

Johnny