Last active
September 2, 2022 14:27
-
-
Save ckhung/ee209ca9ca2ddef82c956b67b2e3cc71 to your computer and use it in GitHub Desktop.
add an "area" field and a "centroid" field to feature.geometry for (multi)polygons in a geojson file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# coding=utf-8 | |
# -*- coding: utf-8 -*- | |
# This script adds an "area" field (in square meters) | |
# and a "centroid" field to feature.geometry | |
# for Polygon's and MultiPolygon's in a geojson file. | |
# Usage example: | |
# ./gj-area-centroid.py university.geojson | |
# The computation is not exact. It is a planar approximation. | |
# Yet the error is typically less than 1/100000 for areas | |
# no larger than a few square kilometers in lower latitudes. | |
# (My 243 test cases are universities in Taiwan, | |
# between 22-25 degrees North.) | |
# If you have this true area computation package | |
# https://pypi.python.org/pypi/area | |
# installed in your system, you can add the -e flag, e.g. | |
# ./gj-area-centroid.py -e 100000 university.geojson | |
# to see the error rate (multiplied by the given number) | |
# as compared to the result of the (true) spherical formula. | |
import argparse, re, warnings, atexit, json, sys, math | |
def ring_area_centroid(ringlist): | |
# Shift all coords to be relative to the approximate | |
# middle of the list of rings so that the computation | |
# will be more numerically stable. | |
all_x = [v[0] for ring in ringlist for v in ring] | |
all_y = [v[1] for ring in ringlist for v in ring] | |
mid = [(max(all_x)+min(all_x))/2, (max(all_y)+min(all_y))/2] | |
area = cx = cy = 0 | |
# no need to distinguish between the outer rings and | |
# the inner rings because the inner rings' areas | |
# will be negative anyway | |
for c in ringlist: | |
n = len(c) | |
# last point is the same as the first, | |
# so no need to wrap around. | |
for i in range(0, n-1): | |
x0 = c[i][0] - mid[0] | |
y0 = c[i][1] - mid[1] | |
x1 = c[i+1][0] - mid[0] | |
y1 = c[i+1][1] - mid[1] | |
cross = x0*y1 - x1*y0 | |
area += cross | |
# https://en.wikipedia.org/wiki/Centroid#Centroid_of_a_polygon | |
cx += (x0 + x1) * cross | |
cy += (y0 + y1) * cross | |
# https://en.wikipedia.org/wiki/World_Geodetic_System | |
r = 6378137*math.pi/180 | |
return ( | |
area*r*r*math.cos(mid[1]*math.pi/180)/2, | |
[mid[0]+cx/3/area, mid[1]+cy/3/area] | |
) | |
def geom_area_centroid(geom): | |
if geom['type'] == 'MultiPolygon': | |
ringlist = [ring for poly in geom["coordinates"] for ring in poly] | |
else: | |
ringlist = geom["coordinates"] | |
return ring_area_centroid(ringlist) | |
def r_area_centroid(data, areaFN='area', centroidFN='centroid'): | |
# recursively compute area and centroid | |
if type(data) is list: | |
return [r_area_centroid(x, areaFN=areaFN, centroidFN=centroidFN) for x in data] | |
elif type(data) is dict: | |
if not 'type' in data: | |
return data | |
if 'Feature' in data['type']: # "Feature" or "FeatureCollection" | |
return { k: r_area_centroid(data[k], areaFN=areaFN, centroidFN=centroidFN) for k in data.keys() } | |
elif 'Polygon' in data['type']: # "Polygon" or "MultiPolygon" | |
(data[areaFN], data[centroidFN]) = geom_area_centroid(data) | |
if args.error > 0: | |
data['area_true'] = area.area(data) | |
data['area_error'] = (data[areaFN] - data['area_true'])/data['area_true'] * args.error | |
return data | |
else: | |
return data | |
else: | |
return data | |
parser = argparse.ArgumentParser( | |
description='compute area of polygons in a json file', | |
formatter_class=argparse.ArgumentDefaultsHelpFormatter) | |
parser.add_argument('-a', '--area', type=str, | |
default='area', help='field name of area') | |
parser.add_argument('-c', '--centroid', type=str, | |
default='centroid', help='field name of centroid') | |
parser.add_argument('-e', '--error', type=float, | |
default=-1, help='comput error rate and multiply it by this positive number. (e.g. "-e 100" for "percentage") needs the area package.') | |
parser.add_argument('FILE', help='the geojson file') | |
args = parser.parse_args() | |
if args.error > 0: | |
import area | |
with open(args.FILE, 'r') as gjfile: | |
data = json.load(gjfile) | |
# https://stackoverflow.com/a/5648769 | |
# Python print unicode strings in arrays as characters, not code points | |
# https://stackoverflow.com/questions/2596714/why-does-python-print-unicode-characters-when-the-default-encoding-is-ascii | |
# print repr(r_area_centroid(data)).decode('unicode-escape') | |
# https://stackoverflow.com/questions/18337407/saving-utf-8-texts-in-json-dumps-as-utf8-not-as-u-escape-sequence | |
print json.dumps(r_area_centroid(data, areaFN=args.area, centroidFN=args.centroid), ensure_ascii=False, indent=2).encode('utf8') | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment