eqzx
eqzx

Reputation: 5579

Google Maps heatmap layer point radius

I want to be able to specify the radius of points effectively in meters. The API is configured so that the radius property is held to be constant for pixels, so that zooming in causes the heatmap to erode (I know you can make the heatmap not erode with the dissipating property, but this raises other issues, i.e., having to manually mess with the radius to get my heatmap to display properly. Here is the heatmaps reference.

Specifically, I'm trying to display a probability distribution on a map. I have the distribution in image form, and want to map the 0-1 weights to a heatmap layer. (I can, and don't want to, overlay the images).

Any suggestions?

Upvotes: 12

Views: 14216

Answers (5)

Elchanan Vol
Elchanan Vol

Reputation: 1

work for me with

        const zoomMetersPerPixel  = {
        0: 156543,
        1: 78272,
        2: 39136,
        3: 19568,
        4: 9784,
        5: 4892,
        6: 2446,
        7: 1223,
        8: 611.5,
        9: 305.8,
        10: 152.9,
        11: 76.4,
        12: 38.22,
        13: 19.11,
        14: 9.56,
        15: 4.78,
        16: 2.39,
        17: 1.2,
        18: 0.5972,
        19: 0.2986,
        20: 0.1493,
        21: 0.0746,
        22: 0.0373,
        23: 0.0187
    };
    const baseZoom = 16;
    const baseRadius = 1000;
    var calculateRadius =  () => {
        const currentZoom = map.getZoom();
        console.log(`Zoom Level: ${currentZoom}`);
        const scaleFactor = zoomMetersPerPixel[baseZoom] / zoomMetersPerPixel[currentZoom];
        const adjustedRadius = baseRadius * scaleFactor;
        console.log(`Adjusted Radius: ${adjustedRadius}`);
        return adjustedRadius };
    const heatmap = new google.maps.visualization.HeatmapLayer({
        data: this.geojsonData,
        map: map,
        radius: calculateRadius(),
        opacity: 0.7,
        // maxIntensity: 10 // Adjust based on your MEDIAN values range
    });
    
    map.addListener('zoom_changed', () => {
        heatmap.setOptions({radius: calculateRadius()});
    });

Upvotes: 0

Aaron Bramson
Aaron Bramson

Reputation: 1288

Based on a Google group forum post and this GIS post, I came up with a simple yet complete solution along the same lines.

First, define a function to get the radius in meters for a given zoom level: Because there are scaling differences for different latitudes, you need to feed in someLatValue, for example the center of the map your plan on using. Although an approximation, it will be good enough for accurate results up to the size of a small country. You also need to specify the size of the radius you want in meters.

You could change the function to read these values in as parameters if you prefer (e.g., getting the lat of the center of the current map view and/or a radius based on property of the data), but if they are static, this making them globals is easier.

var someLatValue = 35.726332;
var desiredRadiusInMeters = 1500;

function getHeatmapRadius(){
  metersPerPx = 156543.03392 * Math.cos(someLatValue * Math.PI / 180) / Math.pow(2,theMap.getZoom());
  return desiredRadiusInMeters / metersPerPx;
};

This returns the (approximate) radius in pixels for a desired number of meters and zoom level around a particular latitude. The value 156543.03392 is based on the approximate radius of the Earth that google actually uses for Google Maps.

So, say you have a heatmap like this:

fixedRadiusHeatmap = new google.maps.visualization.HeatmapLayer({
  data: myCoords,
  map: theMap
});

In order to set the initial view, just call the function before adding the heatmap to your map.

fixedRadiusHeatmap.setOptions({radius: getHeatmapRadius()});
fixedRadiusHeatmap.setMap(theMap);

Then you need to reset the radius every time the map is zoomed, which can be done like this:

google.maps.event.addListener(theMap, 'zoom_changed', function () {
  fixedRadiusHeatmap.setOptions({radius: getHeatmapRadius()});
});

In my case it lags a bit, so it shows the (stupidly aggregated and poorly thought out) default heatmap before the fixed radius one appears. Maybe there is a way to delay the rendering to make the transition smoother, but that's a different problem.

Upvotes: 2

junlop
junlop

Reputation: 13

I solved this by using the listener that @lccarrasco used but in my getNewRadius() function i made the radius change relative to the zoom.

ie. var radius = (somemultiplicationfactor)/(Math.pow(2,(20-zoom)));

This works as the zoom ratio is 2:1 for each zoom

Upvotes: 1

schenkman
schenkman

Reputation: 104

For anyone who'd like to have a nicely packaged coffeescript version of @lccarrasco's jsbin example, you can view the gist of the MercatorProjection coffeescript class I created using his code.

Once you have the class loaded, you can use it with the following:

map = new google.maps.Map(...)
heatmap = new google.maps.visualization.HeatmapLayer({map: map})
google.maps.event.addListener(map, 'zoom_changed', () ->
  projection = new MercatorProjection()
  heatmap.setOptions(radius: projection.getNewRadius(map, 15))
)

Where '15' is the radius in meters which you can play with or set programmatically by other means to get your heatmap to look like you want it.

Upvotes: 1

lccarrasco
lccarrasco

Reputation: 2051

Ok, I tried some things:

Using the Mercator Projection example (check the source) to extract the x,y pixel coordinates of any point from a latLng, to later use the geometry library, specifically the computeOffset function get another latLng a distance "DM" (in meters) to the right of the previous one, get the difference (in pixels) as an absolute value "DP" and from there you get your "pixelsPerMeter" ratio DP/DM.

So then, if you want your radius to be 100 meters you just set the properties to {radius:Math.floor(desiredRadiusPerPointInMeters*pixelsPerMeter)}

And to handle the change in zoom just use a listener

 google.maps.event.addListener(map, 'zoom_changed', function () {
          heatmap.setOptions({radius:getNewRadius()});
      });

I uploaded a small example (try zooming), you can check if the distance looks right with the button on the bottom.

Upvotes: 14

Related Questions