Thursday, 17 March 2016

mongodb - How to check if GeoJSON feature is in rectangular shape and find corner points?


I have a collection of GeoJSON features as Polygon & MultiPolygon which are saved in MongoDB. Many among them are in rectangular or square shape, while others are odd shaped. How can I find all the four corner (top left, top right, bottom left and bottom right) points if that feature is in rectangular or square shape?


I first tried to filter those features which has only five co-ordinates, so that those points will be corner co-ordinates essentially. But some of them are having more than five co-ordinates but as shape they are rectangle or square. Check below given feature examples. First is odd shaped feature, second is rectangle with redundant points and third is rectangle with 4+1 points.


I checked TurfJS, but didn't find any helpful method. BBox gives me non-rotated rectangle, which is not useful because it can also give bbox for non-rectangular shape also.



{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[

[
[
-102.61383274599996,
32.37585257400008
],
[
-102.61764297999997,
32.375000929000066
],
[

-102.62239479299996,
32.388750307000066
],
[
-102.60775890199994,
32.392205895000075
],
[
-102.60566771099997,
32.392699906000075

],
[
-102.60510962499995,
32.39093294600008
],
[
-102.61772064899998,
32.387951290000046
],
[

-102.61383274599996,
32.37585257400008
]
]
]
]
}
},
{
"type": "Feature",

"properties": {},
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
-102.59602204999999,
32.36491866700004
],

[
-102.59952111499996,
32.37500140000003
],
[
-102.60087906199999,
32.378914276000046
],
[
-102.58443896599994,

32.38292495700006
],
[
-102.58184811899997,
32.37500015000006
],
[
-102.57980711899995,
32.36875659300006
],

[
-102.59602204999999,
32.36491866700004
]
]
]
]
}
},
{

"type": "Feature",
"properties": {},
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
-102.62506373399998,
32.34304188700003

],
[
-102.64139887699997,
32.33901489700003
],
[
-102.64604796299994,
32.352882700000066
],
[

-102.62962431499994,
32.35679300500004
],
[
-102.62506373399998,
32.34304188700003
]
]
]
]

}
}
]
}

https://bl.ocks.org/Xyroid/raw/800dafa55111ce139b8158c62f858c98/



Answer



You could sum up angles. If you want to stick to Turf.js, try


function isRectangle(turfInputPolygon, threshold) {


var threshold = threshold || 2;

var turfPolygon = turfInputPolygon;

if (turf.booleanClockwise(turf.polygonToLine(turfPolygon).features[0])) {

turfPolygon = turf.rewind(turfPolygon);

};


var turfPolygonPts = turf.explode(turfPolygon);

turfPolygonPts.features.push(turfPolygonPts.features[1]);

var rightAngles = 0;
var sumAngles = 0;

for (var i = 1, len = turfPolygonPts.features.length; i < len - 1; i++) {

var b1 = turf.bearing(turfPolygonPts.features[i - 1], turfPolygonPts.features[i]);

var b2 = turf.bearing(turfPolygonPts.features[i], turfPolygonPts.features[i + 1]);

var angle = Math.min((b1 - b2 + 360) % 360, (b2 - b1 + 360) % 360);

sumAngles += angle;

if ((90 - threshold) <= angle && angle <= (90 + threshold)) rightAngles ++;

};


return rightAngles == 4 && ((360 - threshold) <= sumAngles && sumAngles <= (360 + threshold));

};

JSFiddle


This is just a quick hack for demonstration, but in principle this should work. The function



  • explodes the (Multi)Polygon feature into Point features

  • appends the second feature to the end of the point feature array
    (cheap trick to be able to calculate the last angle)


  • calculates the angles between two adjacent lines made from two consecutive points

  • returns true if there are 4 right angles and the sum of all angles is 360°
    (both calculations can be adjusted in sensitivity for exact values by the threshold value)




Update:


Since I just realzed you also want the corner points; this function returns those features that sit on the 90° angles if the shape is found to be rectangular, or false if not:


function getCornerPts(turfInputPolygon, threshold) {

var threshold = threshold || 1;


var rightAngles = 0;
var sumAngles = 0;

var cornerPts = [];

var turfPolygon = turfInputPolygon;

if (turf.booleanClockwise(turf.polygonToLine(turfPolygon).features[0])) {


turfPolygon = turf.rewind(turfPolygon);

};

var turfPolygonPts = turf.explode(turfPolygon);

turfPolygonPts.features.push(turfPolygonPts.features[1]);

for (var i = 1, len = turfPolygonPts.features.length; i < len - 1; i++) {


var b1 = turf.bearing(turfPolygonPts.features[i - 1], turfPolygonPts.features[i]);
var b2 = turf.bearing(turfPolygonPts.features[i], turfPolygonPts.features[i + 1]);

var angle = Math.min((b1 - b2 + 360) % 360, (b2 - b1 + 360) % 360);

sumAngles += angle;

if ((90 - threshold) <= angle && angle <= (90 + threshold)) {

rightAngles ++;


cornerPts.push(turfPolygonPts.features[i]);

};

};

return (rightAngles == 4 && ((360 - threshold) <= sumAngles && sumAngles <= (360 + threshold))) ? cornerPts : false;

};


JSFiddle


No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...