7

I've got a map that uses a geoJSON file to draw the countries. I then want to draw a circle centered on each country. But for countries with several bounding regions (the US has mainland, Hawaii, Alaska) I want the circle on the largest bounding region. I'm trying to do this by comparing the areas of the different bounding regions, but it isn't working for reasons I can't understand.

Here's an example from the geoJSON, showing how Australia has multiple bounding regions:

{"type":"Feature","properties":{"name":"Australia"},"geometry":{"type":"MultiPolygon","coordinates":[[[[145.397978,-40.792549],[146.364121,-41.137695],[146.908584,-41.000546],[147.689259,-40.808258],[148.289068,-40.875438],[148.359865,-42.062445],[148.017301,-42.407024],[147.914052,-43.211522],[147.564564,-42.937689],[146.870343,-43.634597],[146.663327,-43.580854],[146.048378,-43.549745],[145.43193,-42.693776],[145.29509,-42.03361],[144.718071,-41.162552],[144.743755,-40.703975],[145.397978,-40.792549]]],[[[143.561811,-13.763656],[143.922099,-14.548311],[144.563714,-14.171176],[144.894908,-14.594458],[145.374724,-14.984976],[145.271991,-15.428205],[145.48526,-16.285672],[145.637033,-16.784918],[145.888904,-16.906926],[146.160309,-17.761655],[146.063674,-18.280073],[146.387478,-18.958274],[147.471082,-19.480723],[148.177602,-19.955939],[148.848414,-20.39121],[148.717465,-20.633469],[149.28942,-21.260511],[149.678337,-22.342512],[150.077382,-22.122784],[150.482939,-22.556142],[150.727265,-22.402405],[150.899554,-23.462237],[151.609175,-24.076256],[152.07354,-24.457887],[152.855197,-25.267501],[153.136162,-26.071173],[153.161949,-26.641319],[153.092909,-27.2603],[153.569469,-28.110067],[153.512108,-28.995077],[153.339095,-29.458202],[153.069241,-30.35024],[153.089602,-30.923642],[152.891578,-31.640446],[152.450002,-32.550003],[151.709117,-33.041342],[151.343972,-33.816023],[151.010555,-34.31036],[150.714139,-35.17346],[150.32822,-35.671879],[150.075212,-36.420206],[149.946124,-37.109052],[149.997284,-37.425261],[149.423882,-37.772681],[148.304622,-37.809061],[147.381733,-38.219217],[146.922123,-38.606532],[146.317922,-39.035757],[145.489652,-38.593768],[144.876976,-38.417448],[145.032212,-37.896188],[144.485682,-38.085324],[143.609974,-38.809465],[142.745427,-38.538268],[142.17833,-38.380034],[141.606582,-38.308514],[140.638579,-38.019333],[139.992158,-37.402936],[139.806588,-36.643603],[139.574148,-36.138362],[139.082808,-35.732754],[138.120748,-35.612296],[138.449462,-35.127261],[138.207564,-34.384723],[137.71917,-35.076825],[136.829406,-35.260535],[137.352371,-34.707339],[137.503886,-34.130268],[137.890116,-33.640479],[137.810328,-32.900007],[136.996837,-33.752771],[136.372069,-34.094766],[135.989043,-34.890118],[135.208213,-34.47867],[135.239218,-33.947953],[134.613417,-33.222778],[134.085904,-32.848072],[134.273903,-32.617234],[132.990777,-32.011224],[132.288081,-31.982647],[131.326331,-31.495803],[129.535794,-31.590423],[128.240938,-31.948489],[127.102867,-32.282267],[126.148714,-32.215966],[125.088623,-32.728751],[124.221648,-32.959487],[124.028947,-33.483847],[123.659667,-33.890179],[122.811036,-33.914467],[122.183064,-34.003402],[121.299191,-33.821036],[120.580268,-33.930177],[119.893695,-33.976065],[119.298899,-34.509366],[119.007341,-34.464149],[118.505718,-34.746819],[118.024972,-35.064733],[117.295507,-35.025459],[116.625109,-35.025097],[115.564347,-34.386428],[115.026809,-34.196517],[115.048616,-33.623425],[115.545123,-33.487258],[115.714674,-33.259572],[115.679379,-32.900369],[115.801645,-32.205062],[115.689611,-31.612437],[115.160909,-30.601594],[114.997043,-30.030725],[115.040038,-29.461095],[114.641974,-28.810231],[114.616498,-28.516399],[114.173579,-28.118077],[114.048884,-27.334765],[113.477498,-26.543134],[113.338953,-26.116545],[113.778358,-26.549025],[113.440962,-25.621278],[113.936901,-25.911235],[114.232852,-26.298446],[114.216161,-25.786281],[113.721255,-24.998939],[113.625344,-24.683971],[113.393523,-24.384764],[113.502044,-23.80635],[113.706993,-23.560215],[113.843418,-23.059987],[113.736552,-22.475475],[114.149756,-21.755881],[114.225307,-22.517488],[114.647762,-21.82952],[115.460167,-21.495173],[115.947373,-21.068688],[116.711615,-20.701682],[117.166316,-20.623599],[117.441545,-20.746899],[118.229559,-20.374208],[118.836085,-20.263311],[118.987807,-20.044203],[119.252494,-19.952942],[119.805225,-19.976506],[120.85622,-19.683708],[121.399856,-19.239756],[121.655138,-18.705318],[122.241665,-18.197649],[122.286624,-17.798603],[122.312772,-17.254967],[123.012574,-16.4052],[123.433789,-17.268558],[123.859345,-17.069035],[123.503242,-16.596506],[123.817073,-16.111316],[124.258287,-16.327944],[124.379726,-15.56706],[124.926153,-15.0751],[125.167275,-14.680396],[125.670087,-14.51007],[125.685796,-14.230656],[126.125149,-14.347341],[126.142823,-14.095987],[126.582589,-13.952791],[127.065867,-13.817968],[127.804633,-14.276906],[128.35969,-14.86917],[128.985543,-14.875991],[129.621473,-14.969784],[129.4096,-14.42067],[129.888641,-13.618703],[130.339466,-13.357376],[130.183506,-13.10752],[130.617795,-12.536392],[131.223495,-12.183649],[131.735091,-12.302453],[132.575298,-12.114041],[132.557212,-11.603012],[131.824698,-11.273782],[132.357224,-11.128519],[133.019561,-11.376411],[133.550846,-11.786515],[134.393068,-12.042365],[134.678632,-11.941183],[135.298491,-12.248606],[135.882693,-11.962267],[136.258381,-12.049342],[136.492475,-11.857209],[136.95162,-12.351959],[136.685125,-12.887223],[136.305407,-13.29123],[135.961758,-13.324509],[136.077617,-13.724278],[135.783836,-14.223989],[135.428664,-14.715432],[135.500184,-14.997741],[136.295175,-15.550265],[137.06536,-15.870762],[137.580471,-16.215082],[138.303217,-16.807604],[138.585164,-16.806622],[139.108543,-17.062679],[139.260575,-17.371601],[140.215245,-17.710805],[140.875463,-17.369069],[141.07111,-16.832047],[141.274095,-16.38887],[141.398222,-15.840532],[141.702183,-15.044921],[141.56338,-14.561333],[141.63552,-14.270395],[141.519869,-13.698078],[141.65092,-12.944688],[141.842691,-12.741548],[141.68699,-12.407614],[141.928629,-11.877466],[142.118488,-11.328042],[142.143706,-11.042737],[142.51526,-10.668186],[142.79731,-11.157355],[142.866763,-11.784707],[143.115947,-11.90563],[143.158632,-12.325656],[143.522124,-12.834358],[143.597158,-13.400422],[143.561811,-13.763656]]]]},"id":"AUS"},

And here's the code where I try to compare areas of different bounding regions. In that if statement, I'm trying to figure out if the country has more than one bounding region (that seems to work) and then in the for block, pick the largest one.

The problem: The "area" values I'm getting are all 0 and coords is always being chosen from the first bounding region, rather than the largest.

function calculateCountryCenter(country) {

    var coords;

    //Check if the country has more than one bounding region.
    if (country.geometry.coordinates.length > 1) {

        coords = country.geometry.coordinates[0][0];

        var regionArea = path.area(country.geometry.coordinates[0]);

        for (var i=0; i<country.geometry.coordinates.length; i++) {
            if (path.area(country.geometry.coordinates[i]) > regionArea) {

                coords = country.geometry.coordinates[i][0];
            }
        }     

    } else {    
        coords = country.geometry.coordinates[0];
    }

    var averageCoords = [0,0];
    coords.forEach(function(coord) {
        averageCoords[0] += coord[0]
        averageCoords[1] += coord[1]
    });

    averageCoords[0] = averageCoords[0] / coords.length
    averageCoords[1] = averageCoords[1] / coords.length
    return averageCoords;

}

Here's the definition of the path.

      var path = d3.geo.path().projection(projection)

Any guidance would be much appreciated. Many thanks.

ACPrice
  • 667
  • 2
  • 10
  • 25
  • What do you mean by "not work"? Are you getting any error messages? – Lars Kotthoff Apr 15 '13 at 16:03
  • Sorry, good question, I'll edit my original post. What's not working is that the value I'm getting for those area calculations seems to be zero. And the circles are getting drawn on the first bounding region no matter what. – ACPrice Apr 15 '13 at 16:12
  • Can you show us the definition of `path` as well please? – Lars Kotthoff Apr 15 '13 at 16:22
  • Yep, will update now. – ACPrice Apr 15 '13 at 16:45
  • The complete file is here: https://github.com/ACPrice/Flights/blob/master/flights/flights.html – ACPrice Apr 15 '13 at 16:48
  • According to the documentation, you have to pass a complete feature to `path.area`, i.e. not just a list of coordinates. Does it work if you pass an entire country? – Lars Kotthoff Apr 15 '13 at 17:27
  • I'll try that. But I thought path.area could also take just a Polygon or a MultiPolygon. – ACPrice Apr 15 '13 at 19:11
  • You could try to construct a Polygon using a subset of the coordinates. – Lars Kotthoff Apr 15 '13 at 19:13
  • Ok, right. So to do that do I have to parse every array in the coordinates into point format? Or do you happen to know of an easier way? (Thanks for the help, BTW) – ACPrice Apr 15 '13 at 22:18
  • It might be easiest to extract the subset of coordinates you want and put them into an object that looks like the original one (i.e. right number of nested coordinates, "type" attribute etc). – Lars Kotthoff Apr 16 '13 at 08:13
  • Hey @LarsKotthoff that's exactly right. It took me a while to get an object that "looks" like the original, but I did in the end. Thanks for your help. – ACPrice Apr 16 '13 at 14:57
  • Ok, I'll add this as an answer for future reference. – Lars Kotthoff Apr 16 '13 at 15:06

3 Answers3

9

You can calculate the area of an arbitrary polygon using the Shoelace algorithm.

function polygonArea(points) {
  var sum = 0.0;
  var length = points.length;
  if (length < 3) {
    return sum;
  }
  points.forEach(function(d1, i1) {
    i2 = (i1 + 1) % length;
    d2 = points[i2];
    sum += (d2[1] * d1[0]) - (d1[1] * d2[0]);
  });
  return sum / 2;
}

polygonArea([[0,0], [4,0], [4,3]]);  // 6

Be aware that counterclockwise polygons will have a positive area, and clockwise polygons will have a negative area. Self-intersecting polygons typically have both clockwise and counterclockwise regions so their area will cancel out.

Ian Mackinnon
  • 13,381
  • 13
  • 51
  • 67
4

Just because nobody has added a fully implemented answer yet, I've decided to answer my own question. Here's code that takes geoJSON, as specified in the original question, and returns the coordinates of the largest bounding region for countries with more than one bounding region. For countries with only one bounding region, it returns the coords of the one bounding region. (I had to modify the code a bit to make it a standalone method here, but I'm pretty sure it's error-free.)

function getLargestBoundingRegion(country) {

var largestRegionCoords;
var path = d3.geo.path()

//Check if the country has more than one bounding region.
if (country.geometry.coordinates.length > 1) {

    var regionToReturn = {        
        "type": "Feature",
        "geometry": {
            "type": "Polygon", 
            "coordinates": country.geometry.coordinates[0]},            
        };

    for (var i=1; i<country.geometry.coordinates.length; i++) {

        var testRegion = {        
                    "type": "Feature",
                "geometry": {
                    "type": "Polygon", 
                    "coordinates": country.geometry.coordinates[i]},            
            };

        if (path.area(testRegion) > path.area(regionToReturn)) {
            regionToReturn = testRegion;    
            largestRegionCoords = country.geometry.coordinates[i][0];    
            }
        }

    } else {    
        largestRegionCoords = country.geometry.coordinates[0];
    }

return largestRegionCoords;
ACPrice
  • 667
  • 2
  • 10
  • 25
1

The path.area function expects a feature and doesn't work with just a list of coordinates. The easiest way to make it work is probably to copy the original object, delete all the coordinates that you're not interested in and pass that to path.area. The code would look something like

for (var i=0; i<country.geometry.coordinates.length; i++) {
    var copy = JSON.parse(JSON.stringify(country));
    copy.geometry.coordinates = [copy.geometry.coordinates[i]];
    path.area(copy);
    ...
}
Lars Kotthoff
  • 107,425
  • 16
  • 204
  • 204
  • Hey, thanks. I implemented a slightly more explicit solution last night. Just added it as another answer. Your approach may be a bit more streamlined. – ACPrice Apr 16 '13 at 15:34