Remove doughnut polygons

1050
9
Jump to solution
09-10-2018 10:17 AM
JamesCrandall
MVP Frequent Contributor

My code is expecting a single ring of coordinates that will convert into arcpy.Polygon(<polyArray>, <desired spatial ref>) and has worked just fine.  However, there some instances where multiple rings are encountered (doughnut polys) and I now need to handle appropriately.

Dissolve, exterior ring/poly, etc. any ideas or assistance to convert the following JSON into a SINGLE arcpy.Polygon()???

Thanks!

{
  "features": [
    {
      "geometry": {
        "rings": [
          [
            [
              -9101613.3137,
              3036108.4844999984
            ],
            [
              -9101624.0627,
              3036107.2901999988
            ],
            [
              -9101637.9523,
              3036107.067400001
            ],
            [
              -9101702.9795,
              3036105.580600001
            ],
            [
              -9101714.2895,
              3036105.3200999983
            ],
            [
              -9101716.2594,
              3036335.3596
            ],
            [
              -9101603.4394,
              3036337.8596
            ],
            [
              -9101604.4099,
              3036452.850299999
            ],
            [
              -9101605.0145,
              3036567.2518999986
            ],
            [
              -9101593.5307,
              3036568.089899998
            ],
            [
              -9101492.4093,
              3036570.249499999
            ],
            [
              -9101379.5599,
              3036572.659699999
            ],
            [
              -9101378.65,
              3036457.8000999987
            ],
            [
              -9101153.0394,
              3036462.66
            ],
            [
              -9101152.4496,
              3036393.209899999
            ],
            [
              -9101152.0695,
              3036347.8797999993
            ],
            [
              -9101377.6404,
              3036342.839400001
            ],
            [
              -9101374.448,
              3036230.3060000017
            ],
            [
              -9101373.2537,
              3035998.6063
            ],
            [
              -9101601.3704,
              3035993.828899998
            ],
            [
              -9101600.1761,
              3036107.2901999988
            ],
            [
              -9101600.445,
              3036118.136
            ],
            [
              -9101613.3137,
              3036108.4844999984
            ]
          ],
          [
            [
              -9101602.5648,
              3036201.642099999
            ],
            [
              -9101603.7591,
              3036165.8123000003
            ],
            [
              -9101601.3704,
              3036139.5370000005
            ],
            [
              -9101600.8702,
              3036135.285100002
            ],
            [
              -9101602.5248,
              3036202.0218
            ],
            [
              -9101602.5648,
              3036201.642099999
            ]
          ],
          [
            [
              -9101600.7696,
              3036290.5020999983
            ],
            [
              -9101600.6783,
              3036280.322900001
            ],
            [
              -9101600.1761,
              3036281.6620999984
            ],
            [
              -9101600.7696,
              3036290.5020999983
            ]
          ]
        ]
      }
    }
  ]
}
0 Kudos
1 Solution

Accepted Solutions
JoshuaBixby
MVP Esteemed Contributor
import json

json_string = # 
json_dict = json.loads(json_string)
json_dict['features'][0]['geometry']['rings'] = json_dict['features'][0]['geometry']['rings'][0:1]
json_string = json.dumps(json_dict)

View solution in original post

9 Replies
JamesCrandall
MVP Frequent Contributor

I think it's just easier to insert the rings as a single polygon feature into an "in_memory" feature class first.  What I didn't reveal was what ultimately I was doing -- clipping.

The issue I had was that I was using a polygon to clip with but because there were multiple rings in each feature I was not correctly handling things.  To simplify it all, I can just insert the rings from a feature into a feature class and then execute my clip from two whole feature classes.

Sorry there's little detail in this but I think I have it covered.

0 Kudos
DanPatterson_Retired
MVP Emeritus

I hate json format, so I usually convert everything to numpy arrays, and do the checks there.

if np.array(rings).ndim !=2:
    sub = rings[0]
else:
    sub = rings

In your example, the array has 3 unequal sub lists, so it has ndim == 1.

If you extract the first sublist, it returns an array with ndim == 2, so you know it is a single part shape with no holes.

If ndim return 3 or more, then it is multidimensional and can consist of parts and/or rings

JamesCrandall
MVP Frequent Contributor

it's maddening. 

Thanks for that tip Dan!

0 Kudos
JoshuaBixby
MVP Esteemed Contributor
import json

json_string = # 
json_dict = json.loads(json_string)
json_dict['features'][0]['geometry']['rings'] = json_dict['features'][0]['geometry']['rings'][0:1]
json_string = json.dumps(json_dict)
JamesCrandall
MVP Frequent Contributor

Awesome.  Is that the "boundary" or most outer ring?

Thanks!

0 Kudos
JoshuaBixby
MVP Esteemed Contributor

Yes.  Although Geometry objects—Common Data Types | ArcGIS for Developers doesn't explicitly state it, Reading geometries—Help | ArcGIS Desktop does for Python, which is the same for Esri JSON:

If a polygon contains holes, it consists of a number of rings. The array of point objects returned for a polygon contains the points for the exterior ring and all inner rings. The exterior ring is always returned first, followed by inner rings...
JamesCrandall
MVP Frequent Contributor

Completely missed that looking at the docs.

Thanks again!

0 Kudos
JoshuaBixby
MVP Esteemed Contributor

Although I think the accepted answer is the best approach, you could process the JSON string using 7.2. re — Regular expression operations — Python 2.7.15 documentation.

import re

json_str = # string representing json containing geometry with rings
geom_pat = re.compile(r"""
       ([ \t]*"geometry":\s*\{\s*"rings":\s*\[[ \t]*\n)   # geometry section header
       ([\s\S]*?)                                         # rings
       ([ \t]*\]\s*\}[ \t]*\n)                            # geometry section footer
    """,
    re.X
)
ring_pat = re.compile(r"(\s*\[[\s\S]*?\]\s*\])")

outer_ring = ring_pat.findall(geom_pat.search(json_str).group(2))[0]
json_str = geom_pat.sub(r"\g<1>" + outer_ring + r"\n\g<3>", json_str)
JamesCrandall
MVP Frequent Contributor

Much appreciated!  But.. eww.  I'd have a difficult time translating this into product documentation in the event another developer takes over this implementation!  I've deployed a version that simply creates an in_memory feature class with the polygon(s) and that participates in a clip process --- I had been only using the polygon itself to clip with, and I'll likely go back in and change it to your previous solution.  Ultimately this all gets wrapped up into GP tool and published as a service so I'd prefer to keep things like map services and features as-is rather than in_memory fc's and such.

0 Kudos