Leaflet screen distance to LatLng distance - javascript

I want to combine markers based on the zoom level. I'm not using Markercluster but my own algorithm to detect which markers should be combined. This algorithm works perfectly fine. The only thing I have to add is a zoom-based condition when the markers should be combined. Currently, all markers within a distance of 0.001 get combined. What I want is that every marker within a distance of 0.5cm on the screen gets combined. So I need a function that converts a distance in cm, px, or something else into a distance in degree.
An example: On zoom-level 18, two markers with a distance of 0.00005 in the longitude have a distance of nearly 0.3cm on my screen.
EDIT:
So this is what I did:
function in_range(location1, location2, map) {
return get_distance_in_px(location1, location2, map) < 25;
}
function get_distance_in_px(location1, location2, map) {
var p1 = map.latLngToContainerPoint(L.latLng(location1[0], location1[1]));
var p2 = map.latLngToContainerPoint(L.latLng(location2[0], location2[1]));
var a = p1.x - p2.x;
var b = p1.y - p2.y;
return Math.sqrt(a * a + b * b);
}

You can get the containerPoint of the latlngs and then calculate the distance:
map.on('zoomend',(e)=>{
console.log(e);
recalcZoom();
});
var DISTANCE = 20; //px
function recalcZoom() {
var layers = findLayers(map);
var resultLayers = []; //sturcture: {main: layer, childs: [];
layers.forEach((layer)=>{
if(resultLayers.length === 0){ // set the first layer as main layer
resultLayers.push({
main: layer,
childs: []
});
}else{
var found = false;
var mainObj = null;
var lastDis = null;
resultLayers.forEach((rLayer)=>{
var main = rLayer.main;
var p1 = map.latLngToContainerPoint(main.getLatLng());
var p2 = map.latLngToContainerPoint(layer.getLatLng());
var dis = p1.distanceTo(p2);
if(dis <= DISTANCE){ // distance between main layer and current marker is lower then DISTANCE
if(lastDis == null || dis < lastDis) { // a main layer is found, where the distance between them is lower
if(mainObj && mainObj.childs.indexOf(layer) > -1){ // remove the layer from the old main layer childs array
mainObj.splice(mainObj.childs.indexOf(layer),1);
}
rLayer.childs.push(layer);
found = true;
mainObj = rLayer;
}
}
});
if(!found){ // if no main layer is found, add it as new main layer
resultLayers.push({
main: layer,
childs: []
});
}
}
});
console.log(resultLayers);
// Logic to find center of all childs + main
// remove the old layers and add the new clustered layer
// keep in mind, that you have to store somewhere the original markers, else you can't recalc the clusters
}
function findLayers(map) {
// your logic to get the original layers
let layers = [];
map.eachLayer(layer => {
if (layer instanceof L.Marker) {
layers.push(layer);
}
});
return layers;
}
You have to implement by yourself the logic to find center of all childs + main layer, then remove the old layers and add the new clustered layer.
But keep in mind, that you have to store somewhere the original markers, else you can't recalc the clusters.
Little Example: https://jsfiddle.net/falkedesign/ny9s17cb/ (look into the console)

A note from the Leaflet Examples
One of the disadvantages of using a cylindrical projection is that the
scale is not constant, and measuring distances or sizes is not
reliable, especially at low zoom levels.
In technical terms, the cylindrical projection that Leaflet uses is
conformal (preserves shapes), but not equidistant (does not preserve
distances), and not equal-area (does not preserve areas, as things
near the equator appear smaller than they are).
learn more
After that being said you're really getting yourself into so many more problems trying to get the dimension exact where they aren't reliable measurements to start with.

Related

How to traverse an array of 50k accounts and display them on leaflet map?

I have 50k accounts that i have to display on a map as markers. Everything is fine if i call only for maximum 20k accounts but for a larger number of accounts page become unresponsive. I figure out that it take too long to go through all the array of objects. But i don't know how can I optimise the code more.
The elements that are in the loop are not really affect the traverse of the loop. I run it without anything in it and it still takes same amount of time till become unresponsive. The problem is that it has too many element.
Update: i use markerCluster to cluster all the markers. I use canvas in order to be rendered quicker. Also i read that addLayers is better than addLayer and thats why i use a normal list to add all elements and only after to add it in the markerClusterGroup. Map is automatically updated because if i add the markersGroup even if in the first place is empty any modification to the group will be noticed by the map (it has a watcher by default).
When i tested i just saw that the for is too slow.
Map is from leaflet
Thank you in advance
var map = L.map('map', {zoomControl: true, tap: false, preferCanvas:true});
var accounts = event.getParam('accounts');
var markerList = [];
//create a group of markers in order to have control over markers from map
var myRenderer = L.canvas({ padding: 0.5 });
var markers = L.markerClusterGroup( {
chunkedLoading: true,
renderer: myRenderer,
iconCreateFunction: function (cluster) {
var childCount = cluster.getChildCount();
var c = ' marker-cluster-';
if (childCount < 10) {
c += 'small';
}
else if (childCount < 100) {
c += 'medium';
}
else {
c += 'large';
}
return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>',
className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
}
});
map.addLayer(markers);
var markerColor;
var marker_blue = helper.initMarkers ('/resource/markers/markers/marker_blue.png',[30, 40], [22, 44], [-5, -43]);
markerColor = marker_blue;
for (var i=0, len = accounts.length; i<len; i++) {
var account = accounts[i];
var latLng = [account.Latitude, account.Longitude];
var marker = L.marker(latLng, {icon:markerColor},{account: account},{renderer: myRenderer});
markerList.push(marker);
}
markers.addLayers(markerList);

Javascript collision of two point array polygons

I have searched all over, and I have found answers for rectangle circle and sprite collisions. Nothing that provides collision detection between two arrays of points like for example,
var poly1=[
[0,0],
[20,50],
[50,70],
[70,20],
[50,0]
];
// each point from one to the next represent a line in the shape, then the last point connects to the first to complete it.
var poly2=[
[50,30],
[40,90],
[70,110],
[90,70],
[80,20]
];
var collided=arraysCollided(poly1,poly2);
Does anyone know of a library that can do just this? My research has come up with nothing that supports just that, and isnt associated with some game engine library.
For example a collision is triggered true when one or more points is inside the polygon of the other.
SAT.js was the anser for me, I just put every point into SAT.Vector then into SAT.Polygon, then test them with SAT.testPolygonPolygon(SAT.Polygon,SAT.Polygon);
var poly1={
name:"poly2",
x:400,
y:60,
rotation:0,
runner:function(){
},
points:[
[20,-50],
[-30,-50],
[-30,30],
[10,60],
[50,20]
]
};
var poly2={
name:"poly2",
x:50,
y:70,
runner:function(){
this.x+=1;
},
points:[
[-20,-40],
[-60,50],
[10,70],
[50,30],
[30,-20]
]
};
pGon=(poly)=>{
var center=SAT.Vector(0,0);
var pts=[];
for(var i in poly.points){
var point=poly.points[i];
// point = [0:x,1:y]
pts[pts.length]=new SAT.Vector(point[0]+poly.x,point[1]+poly.y);
}
var poly_a=new SAT.Polygon(center,pts);
return poly_a;
};
pCollide=(p1,p2)=>{
var p1_poly=pGon(p1);
var p2_poly=pGon(p2);
var res=new SAT.Response();
var collided=SAT.testPolygonPolygon(p1_poly,p2_poly,res);
console.log(collided);
console.log(res);
return collided;
};
var collided=pCollided(poly1,poly2);
With that, it maps each point to a polygon on the coordinate system, then tests it from there. So collided = true
I checked for if each point of each polygon is in the other polygon. This is the code for checking if a point is in a polygon:
function pip(x, y, polygon) {
let odd = false;
let v = polygon.va; //The vertices array
let j = v.length - 2;
for (let i=0; i<v.length-1; i+=2) {
if ((v[i+1]<= y && v[j+1]>=y || v[j+1]<= y && v[i+1]>=y)
&& (v[i]<=x || v[j]<=x)) {
odd ^= (v[i] + (y-v[i+1])*(v[j]-v[i])/(v[j+1]-v[i+1])) < x;
}
j=i;
}
if(odd === false) odd = 0;
return odd;
}
I got this from Here, but modified it to work for an array like this [x1,y1,x2,y2,x3,y3...]. To make it work for x,y pairs, you would just change the for loops modifier thing and look at polygon.va[i][0] as x and polygon[i][1] as y.

Calculating the volume of a mesh in ThreeJS is greater than bounding box volume

The problem:
Bounding box volume is smaller than volume calculated from mesh.
What I've tried:
First I calculate the volume of a bounding box with the following code:
//loaded .obj mesh object:
var sizer = new THREE.Box3().setFromObject(obj);
console.log(sizer.size().x*sizer.size().z*sizer.size().y);
log output: 197112.65382983384
Then I calculate the volume of the mesh using this solution with this solution with the legacy geometry object in the method "calculateVolume" included below:
console.log(scope.calculateVolume(child));
calculateVolume is part of a class. My class methods are as follows:
threeDeeAsset.prototype.signedVolumeOfTriangle = function(p1, p2, p3) {
var v321 = p3.x*p2.y*p1.z;
var v231 = p2.x*p3.y*p1.z;
var v312 = p3.x*p1.y*p2.z;
var v132 = p1.x*p3.y*p2.z;
var v213 = p2.x*p1.y*p3.z;
var v123 = p1.x*p2.y*p3.z;
return (-v321 + v231 + v312 - v132 - v213 + v123)/6;
}
threeDeeAsset.prototype.calculateVolume = function(object){
var volumes = 0;
object.legacy_geometry = new THREE.Geometry().fromBufferGeometry(object.geometry);
for(var i = 0; i < object.legacy_geometry.faces.length; i++){
var Pi = object.legacy_geometry.faces[i].a;
var Qi = object.legacy_geometry.faces[i].b;
var Ri = object.legacy_geometry.faces[i].c;
var P = new THREE.Vector3(object.legacy_geometry.vertices[Pi].x, object.legacy_geometry.vertices[Pi].y, object.legacy_geometry.vertices[Pi].z);
var Q = new THREE.Vector3(object.legacy_geometry.vertices[Qi].x, object.legacy_geometry.vertices[Qi].y, object.legacy_geometry.vertices[Qi].z);
var R = new THREE.Vector3(object.legacy_geometry.vertices[Ri].x, object.legacy_geometry.vertices[Ri].y, object.legacy_geometry.vertices[Ri].z);
volumes += this.signedVolumeOfTriangle(P, Q, R);
}
return Math.abs(volumes);
}
log output: 336896.1562770668
Checking the source of the vertexes:
I also tried buffergeometry and of course, it's really the same array, but a typed Float32Array and predictably gave the same result:
var vol = 0;
scope.mesh.traverse(function (child) {
if (child instanceof THREE.Mesh) {
var positions = child.geometry.getAttribute("position").array;
for(var i=0;i<positions.length; i+=9){
var t1 = {};
t1.x = positions[i+0];
t1.y = positions[i+1];
t1.z = positions[i+2];
var t2 = {};
t2.x = positions[i+3];
t2.y = positions[i+4];
t2.z = positions[i+5];
var t3 = {};
t3.x = positions[i+6];
t3.y = positions[i+7];
t3.z = positions[i+8];
//console.log(t3);
vol += scope.signedVolumeOfTriangle(t1,t2,t3);
}
}
});
console.log(vol);
log output: 336896.1562770668
The question: Why is my bounding box volume smaller than the calculated volume of a closed mesh?
Perhaps I missing something such as an offset position or maybe I am calculating the bounding box volume wrong. I did several searches on google and stack, which is how I came to find the signedVolumeOfTriangle function above. it seems to be the most common accepted approach.
Could be a problem with winding order, you could try negating the result from signed volume, or reordering arguments.
The winding order will be clockwise or counterclockwise and determines the facing (normal) of the polygon,
volumes += this.signedVolumeOfTriangle(P, Q, R);
Swapping P and R inverts the normal,
volumes += this.signedVolumeOfTriangle(R, Q, P);
This can be complicated by storage techniques like triangle strips, where vertices are shared by adjacent triangles, causing winding order to alternate.
Another problem could be - especially if it works correctly for simple meshes - is degenerate vertices. If you're getting your meshes from an editor, and the mesh has been modified and tweaked a million times (usually the case), it's almost guaranteed to have degenerates.
There may be an option to weld close vertices in the editor that can help, or test with a known good mesh e.g. the Stanford bunny.

Create dot density map using Google Maps

I would like to create a dot density map using Google Maps. I have all the counties of my state outlined, along with their corresponding populations. I want to know how I could place a number of dots randomly within each county to represent the population of that county. We want to make a dot density map instead of a choropleth map because we like the representation better, but I can't figure out how to distribute dots among a polygon outline.
This is a poor example of sort of what I'm looking to make.
Well, I've come to a very inefficient yet suitable solution to my problem. In case anybody would either like to help me improve my method or use it themselves, this is what I did.
I used this answer as a guide to test whether or not a point would fall in a particular polygon. As I create the polygons that outline the border of counties in my state, I add each latitude to one array, and each longitude to another. I then determine min and max values for each array as a bounding box that a point would have to be in in order to fall within the county lines. I then pick random numbers between those mins and maxes and test whether they fall within the county. If they do, I add a marker there. I do those within a loop that counts how many markers are added until it is proportional to the population of that particular county. Here is the code:
function addMarkers() {
var loc = "Resources/CaliforniaCounties.json";
$.getJSON(loc, function (data) {
$.each(data.features, function (key, val) {
var xArray = []; //
var yArray = []; //
var coords = [];
var latlng;
var bounds = new google.maps.LatLngBounds();
var polygon;
$.each(val.geometry.coordinates[0], function (i, item) {
latlng = new google.maps.LatLng(item[1], item[0]);
xArray.push(item[0]); //
yArray.push(item[1]); //
coords.push(latlng);
bounds.extend(latlng);
});
var nverts = xArray.length; //
var maxX = Math.max.apply(null, xArray); //
var maxY = Math.max.apply(null, yArray); //
var minX = Math.min.apply(null, xArray); //
var minY = Math.min.apply(null, yArray); //
polygon = new google.maps.Polygon({
paths: coords,
strokeColor: "#000000",
strokeOpacity: 1,
strokeWeight: 01,
fillColor: "#cccccc",
fillOpacity: .5
});
polygon.center = bounds.getCenter();
addPolygonClickListener(polygon, val);
polygon.setMap(map);
polygonArray[val.properties.Name] = polygon;
var i = 1;
while( i < populations[val.properties.Name] / 10000){
var testX = Math.random() * (maxX - minX) + minX; //
var testY = Math.random() * (maxY - minY) + minY; //
if(pnpoly(nverts, xArray, yArray, testX, testY) == 1){ //
var mlatlng = new google.maps.LatLng(testY, testX); //
var marker = new google.maps.Marker({ position: mlatlng, icon: "Resources/dot.png", map: map }); //
i++;
}
}
});
});
function pnpoly(nvert, vertx, verty, testx, testy)
{
var i, j, c = 0;
for (i = 0, j = nvert-1; i < nvert; j = i++)
{
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
{
c = !c;
}
}
return c;
}
I have a more efficient way to do this. You can use the same features to create color map, which is second, invisible canvas element. In this, each county is a unique color derived from it's index in the feature list. Using getImageData(), you can get the bit map of the canvas. Then, you can use it to test whether your random, bounding box constrained coordinates fall within the county by checking the color of the colormap at that coordinate. This test is a O(1) operation, where as yours looks like it's O(n).
I am using this technique to create a dot density map of counties in china, and the performance is fine. I started with this guy's code example:
https://gist.github.com/awoodruff/94dc6fc7038eba690f43

D3: Finding the area of a geo polygon in d3

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.
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.
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;
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);
...
}

Categories

Resources