2

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/

1

1 Answer 1

4

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

7
  • Thanks a lot man. You saved my day. I will give it a try to my collection. If I got any issue will revert you. If worked then will accept it. Commented Jun 22, 2018 at 14:04
  • I checked it and it's working flawlessly with my collection. But the order of corner points is different. For some features, corner points start from bottom right while for others start from top left and so on. Is it possible to order the corner points in top/bottom right/left direction or (anti)clockwise? Turf has only bool function to check existing point collection. Commented Jun 26, 2018 at 13:51
  • my ultimate aim is to display the text label near the two opposite (not all four) corner point inside feature so if I have consistent direction I can adjust the lat long so the text doesn't appear on corner but near the corner and inside the feature. Commented Jun 26, 2018 at 14:01
  • the 'direction' in my function depends on the order of vertices the polygon was digitized in (the same order they appear in the GeoJSON list), as Turf.js simply generates points in that order. (A sidenote: I noticed your polygons do not follow the right hand rule, officially rendering them invalid!). this is more a data issue than the functions, but I guess you could try and rearrange the array, starting with e.g. the max lat point?
    – geozelot
    Commented Jun 26, 2018 at 14:12
  • Ok, if I ignore the point's position in terms of left/right top/bottom, how can I get the inner position from corner point? I want to pick 1st and 3rd corner point regardless of direction like this prntscr.com/jzl0ia Commented Jun 26, 2018 at 14:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.