# Background

Create a radius around the centre of a city that corresponds to the city's population, taking into consideration the map's current resolution. The populations range from 39 to 1090936, with an average population of 2127. (Most of the populations are an estimated 150 people: hamlets.)

Google Fu returns people looking to calculate the population within a given radius -- this question is the exact opposite: calculate a radius for a given population.

## City Data

The following code calculates a radius for a city based on the city's population:

``````function logn( base, n ) {
return Math.log( n ) / Math.log( base );
}

function addCity( cities, lon, lat, population ) {
cities.push(
new OpenLayers.Feature.Vector(
getPoint( lon, lat ),
{
tooltip: population,
radius: Math.round( logn( 2, population ) ) * 2
}
)
);
}

function getCities() {
var cities = [];

addCity( cities, -116.383062683871, 56.4680558322581, 39 );
addCity( cities, -111.07205136129, 55.6315222967742, 208 );
addCity( cities, -116.019763045161, 58.3878539741935, 739 );
addCity( cities, -113.330203110508, 53.823233143442, 2848 );
addCity( cities, -113.400259352921, 49.7204543294903, 3072 );
addCity( cities, -114.088890487523, 52.3022833957834, 11115 );
addCity( cities, -113.538803745389, 53.2670385040641, 24139 );
addCity( cities, -113.797128210771, 52.281512598323, 65733 );
addCity( cities, -114.052214116579, 51.0350223014551, 1090936 );

return cities;
}
``````

## Radius Style

The radius is styled as follows:

``````var cityStyle = new OpenLayers.Style(
{
fillColor: '#0065BD',
fillOpacity: 0.1,
strokeColor: '#0065BD',
strokeOpacity: 0.25,
strokeWidth: 1.5,
pointRadius: '\${radius}'
}
);

var cityStyleMap = new OpenLayers.StyleMap( cityStyle );

cityLayer = new OpenLayers.Layer.Vector( 'Cities', {
styleMap: cityStyleMap
});
``````

# Problem

The radius is fixed, regardless of zoom level. This leads to incorrectly sized circles, depending on the zoom (i.e., `map.getResolution()`).

# Questions

1. Is log2(n) a good idea to calculate the relative radii (as opposed to a linear calculation, such as simple division)? (This is not overly important.)
2. How would you adjust the radius based on the map's resolution?

Note that the circumscription need not align with the city's centroid; a simple circle with a radius that is relative to the population and zoom level, centred on the city's location is sufficient.

Thank you!

# Related Links

-
Why not create a shapefile (or what have you) with the appropriate geometries in it, then add it as a layer? – nagytech Jul 22 '12 at 9:23
@Geoist - It already is a layer. Also, I want to keep the possibility of changing the displayed through the UI (e.g., a slider that changes the relative scale for all populations). Nor do I want to implement a script that calculates and creates the shapefiles. Also, I don't want to insert shapefiles into the database, transmit them, and render them in OpenLayers when a simple circle (based on a single population value that is already being sent) will suffice. – Dave Jarvis Jul 22 '12 at 9:29

## 2 Answers

You can use context property of OpenLayers.Style object and inside context to define needed function. For example:

``````options = {
div: "map",
layers: [new OpenLayers.Layer.OSM()],
center: [9317951, 7046625],
zoom: 10
}
map = new OpenLayers.Map(options);

style = new OpenLayers.Style(
OpenLayers.Util.extend(
OpenLayers.Feature.Vector.style.default,
{
pointRadius: "\${calculateRadius}"
}
),
{
context: {
calculateRadius: function(f){
return 10000/f.layer.map.getResolution();
}
}
}
);

sm = new OpenLayers.StyleMap({
"default": style
});

vector = new OpenLayers.Layer.Vector('vector', {styleMap: sm});
vector.addFeatures([new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(9317951,7046625))]);
map.addLayer(vector);
``````

Live demo HERE.

-
Exactly what I needed. Thank you. – Dave Jarvis Jul 22 '12 at 18:09

# Solution

In answer to the first question:

``````// Defines a function that is called when applying the city's style template.
var cityContext = {
calculateRadius: function( feature ) {
// Scale the population radius based on the zoom level.
return (1000 / feature.layer.map.getResolution()) * feature.attributes.radius;
}
};

// Define the style for a city's population radius.
var cityTemplate = {
fillColor: 'blue',
fillOpacity: 0.25,
strokeColor: 'blue',
strokeOpacity: 0.65,
strokeWidth: 1.5,
pointRadius: "\${calculateRadius}"
};

var cityStyle = new OpenLayers.Style( cityTemplate, {context: cityContext} );

new OpenLayers.Feature.Vector(
getPoint( lon, lat ),
{
// Define the city's circumscribing region based on population size.
radius: Math.sqrt( population ) / logn( 2, population ) * 0.5
}
)
``````

# Explanation

The following line from the above snippet adjusts the population so that small populations display significantly smaller circles than large populations.

``````radius: Math.sqrt( population ) / logn( 2, population ) * 0.5
``````

Where `logn( 2, population )` is log2(population).

And the following line ensures that the circle's size scales with respect to the map's resolution:

``````return (1000 / feature.layer.map.getResolution()) * feature.attributes.radius;
``````

# Result

The results drmextgis put forward are ideal.

## Zoomed In

It is visually obvious that the population of Calgary is larger than Red Deer.

-