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
]
]
]
}
}
]
}
Solved! Go to Solution.
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)
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.
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
it's maddening.
Thanks for that tip Dan!
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)
Awesome. Is that the "boundary" or most outer ring?
Thanks!
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...
Completely missed that looking at the docs.
Thanks again!
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)
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.