GeoJSON – How to Check if a GeoJSON Feature is Rectangular and Find Corner Points

geojsonmongodbturf

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/

Best 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