Serialize GeoJSON in Python with rounded coordinates - python

I'd like to serialize GeoJSON FeatureCollections in Python with limited precision for their coordinates.
For example, here's a FeatureCollection (represented as dicts and lists in Python):
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "Liberty Island",
"area_sqm": 24950.40123456,
"established": 1875,
"height_ft": 305
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ -74.04715418815613, 40.690994683044906 ],
[ -74.04499769210815, 40.68873311507798 ],
[ -74.04354929924011, 40.689676800252016 ],
[ -74.04715418815613, 40.690994683044906 ]
]
]
}
}
]
}
I can serialize it using json.dumps:
print(json.dumps(fc))
This prints out the JSON:
{"type": "FeatureCollection", "features": [{"type": "Feature", "properties": {"name": "Liberty Island", "area_sqm": 24950.40123456, "established": 1875, "height_ft": 305}, "geometry": {"type": "Polygon", "coordinates": [[[-74.04715418815613, 40.690994683044906], [-74.04499769210815, 40.68873311507798], [-74.04354929924011, 40.689676800252016], [-74.04715418815613, 40.690994683044906]]]}}]}
Those coordinates are far too precise. According to Wikipedia, 7 digits is ~cm precision, which ought to be good enough. What I'm getting is ~nanometer precision.
I'd like to serialize the GeoJSON FeatureCollection with only seven digits of precision for coordinates. Note that I'd like to use Python's default serialization for everything in properties: since the values there could be anything, I can't make any universal claims about how much precision is enough.
My desired output is something like:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "Liberty Island",
"area_sqm": 24950.40123456,
"established": 1875,
"height_ft": 305
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ -74.0471541, 40.6909946 ],
[ -74.0449976, 40.6887331 ],
[ -74.0435492, 40.6896768 ],
[ -74.0471541, 40.6909946 ]
]
]
}
}
]
}

I'm not sure what exact serialization you're after but when it comes to FeatureCollection simplification, this should get you started:
import json
from copy import copy
def simplify_geometries(feat_collection):
new_coll = copy(feat_collection)
old_features = new_coll['features']
new_features = []
for feature in old_features:
geometry = feature['geometry']
coords = shorten_arr(geometry['coordinates'])
geometry['coordinates'] = coords
new_features.append(feature)
new_coll['features'] = new_features
return json.dumps(new_coll)
print(simplify_geometries(
json.loads('{"type": "FeatureCollection",...')
))
where shorten_arr is a trivial recursion:
def shorten_arr(arr, dec_places=7):
if not isinstance(arr, list):
return round(arr, dec_places)
to_ret = []
for n in arr:
if isinstance(n, list):
n = shorten_arr(n, dec_places)
to_ret.append(shorten_arr(n, dec_places))
return to_ret

Related

how to validate properties key in a geojson

i would like to know why the below posted geojson format is invalid. i tried to visualize its data in
http://geojson.io
but nothing gets displayed.
geojson
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[[7.85563468082516,49.90287230375267],[7.855636808249913,49.902782379662085],[7.855776033932631,49.902783753651605],[7.855773906766568,49.902873677746555]]
]
]
},"properties": {"areaOfCoverage":"30"}},
}
Try this:
{
"type": "FeatureCollection",
"features": [
{
"properties":{"areaOfCoverage":"30"},
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
7.85563468082516,
49.90287230375267
],
[
7.855636808249913,
49.902782379662085
],
[
7.855776033932631,
49.902783753651605
],
[
7.85563468082516,
49.90287230375267
]
]
]
}
}
]
}

Delete polygons that have one or more side parts in common

I am trying to solve a particular case of comparison of polygons to others. I have five polygons distributed as in the figure below. The black polygon is the one with the largest area.
There may be other similar cases, the main rule is to remove the smallest polygons among all those that have one or more side portions in common.
The data for this case are in a GeoJson file as follows:
{"type":"FeatureCollection","features":[
{"type":"Feature","properties":{"id":1},"geometry":{"type":"Polygon","coordinates":[[[3.4545135498046875,45.533288879467456],[3.4960556030273433,45.533288879467456],[3.4960556030273433,45.57055337226086],[3.4545135498046875,45.57055337226086],[3.4545135498046875,45.533288879467456]]]}},
{"type":"Feature","properties":{"id":2},"geometry":{"type":"Polygon","coordinates":[[[3.4545135498046875,45.52917023833511],[3.4960556030273433,45.52917023833511],[3.4960556030273433,45.53891018749409],[3.4545135498046875,45.53891018749409],[3.4545135498046875,45.52917023833511]]]}},
{"type":"Feature","properties":{"id":3},"geometry":{"type":"Polygon","coordinates":[[[3.4845542907714844,45.5298015824607],[3.5159683227539062,45.5298015824607],[3.5159683227539062,45.543388795387294],[3.4845542907714844,45.543388795387294],[3.4845542907714844,45.5298015824607]]]}},
{"type":"Feature","properties":{"id":4},"geometry":{"type":"Polygon","coordinates":[[[3.465328216552734,45.542667432984864],[3.4735679626464844,45.542667432984864],[3.4735679626464844,45.5478369923404],[3.465328216552734,45.5478369923404],[3.465328216552734,45.542667432984864]]]}},
{"type":"Feature","properties":{"id":5},"geometry":{"type":"Polygon","coordinates":[[[3.4545138850808144,45.56799974017372],[3.4588050842285156,45.56799974017372],[3.4588050842285156,45.57055290285386],[3.4545138850808144,45.57055290285386],[3.4545138850808144,45.56799974017372]]]}}]}
Is there a solution to delete only the two blue polygons(id 2 and 5)? In python.
By transforming the Polygons into LineString one could look if a Linestring is a portion of another Linestring ? But I don't see how to do it. Or maybe looking to see if the LineString of the black and blue polygons have more than two points in common ? But we can't convert a LineString into more than two points.
The following approach may work for you using shared_paths which correctly calls out the path overlap between polygons 1, 2 and 5:
import json
import shapely as sh
import shapely.ops as ops
import shapely.geometry as geo
with open('./test.json') as f:
features = json.load(f)['features']
for f1 in features:
for f2 in features:
id1 = f1['properties']['id']
id2 = f2['properties']['id']
if int(id1) > int(id2):
s1 = geo.shape(f1['geometry'])
s2 = geo.shape(f2['geometry'])
coll = ops.shared_paths(s1.boundary, s2.boundary)
if not coll.is_empty:
print(f"{id1} and {id2} have shared path")
# update your feature collection etc
I had to reduce the precision to 5 decimal places in your feature geometry for this to work as initially it only detects the overlap between polygon 1 and 2. The shared corner between polygon 1 and 5 is slightly out in your input FeatureCollection:
{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"id": 1
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[3.45451, 45.53328],
[3.49605, 45.53328],
[3.49605, 45.57055],
[3.45451, 45.57055],
[3.45451, 45.53328]
]
]
}
},
{
"type": "Feature",
"properties": {
"id": 2
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[3.45451, 45.52917],
[3.49605, 45.52917],
[3.49605, 45.53891],
[3.45451, 45.53891],
[3.45451, 45.52917]
]
]
}
},
{
"type": "Feature",
"properties": {
"id": 3
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[3.48455, 45.52980],
[3.51596, 45.52980],
[3.51596, 45.54338],
[3.48455, 45.54338],
[3.48455, 45.52980]
]
]
}
},
{
"type": "Feature",
"properties": {
"id": 4
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[3.465328, 45.54266],
[3.473567, 45.54266],
[3.473567, 45.54783],
[3.465328, 45.54783],
[3.465328, 45.54266]
]
]
}
},
{
"type": "Feature",
"properties": {
"id": 5
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[3.454513, 45.56799],
[3.458805, 45.56799],
[3.458805, 45.57055],
[3.454513, 45.57055],
[3.454513, 45.56799]
]
]
}
}
]
}

Not understanding how work with "urn:ogc:def:crs:OGC:1.3:CRS84" in geopandas

I have this geojason file
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "visit_date": "2013-03-27Z", "name": "Mayi-Tatu", "n_workers": 150.0, "mineral": "Gold" }, "geometry": { "type": "Point", "coordinates": [ 29.66033, 1.01089 ] } },
{ "type": "Feature", "properties": { "visit_date": "2013-03-27Z", "name": "Mabanga", "n_workers": 115.0, "mineral": "Gold" }, "geometry": { "type": "Point", "coordinates": [ 29.65862, 1.00308 ] } },
{ "type": "Feature", "properties": { "visit_date": "2013-03-27Z", "name": "Molende", "n_workers": 130.0, "mineral": "Gold" }, "geometry": { "type": "Point", "coordinates": [ 29.65629, 0.98563 ] } },
...
{ "type": "Feature", "properties": { "visit_date": "2017-08-31Z", "name": "Kambasha", "n_workers": 37.0, "mineral": "Cassiterite" }, "geometry": { "type": "Point", "coordinates": [ 29.05973167, -2.25938167 ] } }
]
}
I read this file, with the next code:
filename = "ipis_cod_mines.geojson"
df_congomines_crs84_geo = gpd.read_file(filename)
But when I check the crs property of df_congomines_crs84_geo,
df_congomines_crs84_geo.crs
I got "{'init': 'epsg:4326'}", I don't understand why i don't get the right crs. (first question)
After, I read another dataset for the same area (both data belongs to congo)
df_countries_4326_geo = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
This dataset has crs equal to {'init': 'epsg:4326'}.
When i plot both datasets (without change the crs),
ax = congo_df.plot(alpha=0.5, color='brown', figsize=(11,4))
df_congomines_crs84_geo.plot(ax=ax, column='mineral')
plt.show()
I got the next image:
Image result
Why both image are not overlaped if they belong to the same area??? How can I fix it??? Is this problem related to the UTM zone???(second question)
CRS84 is equivalent to WGS84 for which the standard EPSG code is EPSG:4326. CRS84 was defined in an old geojson spec (2008). Reading a geojson file gives EPSG:4326 as the CRS.

Accessing nested python Dict of coordinates in GeoJSON

I'm making a get request from a REST service and returning a GeoJSON but it recognized as a dict by Python. I'm trying to access the nested list value from the coordinates key and the string value from the Zone_ key. Here is a sample of the data:
data = {
"type": "FeatureCollection",
"crs": {
"type": "name",
"properties": {
"name": "EPSG:4326"
}
},
"features": [{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-80.1135430232235,
33.49892053365546
],
[
-80.1165139377003,
33.499835530094444
],
[
-80.1170369402652,
33.49992051898103
],
[
-80.11707393820328,
33.49992653060032
]
]
]
},
"properties": {
"Zone_": "H"
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-79.62281482439842,
33.289964520159124
],
[
-79.62376378105404,
33.29028749972797
],
[
-79.6247927817771,
33.29068750016911
],
[
-79.62604278223982,
33.29121650014533
]
]
]
},
"properties": {
"Zone_": "I"
}
}
]
}
The problem I am having is I'm stumped with trying to access the coordinates from the geometry key. I'm getting strings back and I don't know how to get to the nested list in coordinates. Here is what I have so far:
for x in data['features']:
for y in x['geometry']:
print(y)
Can someone please help me with this please?
The coordinates are nested inside the geometry of the features. So, you need to access it accordingly.
Try:
for feature in data['features']:
print("Feature coods:")
for cood in feature['geometry']['coordinates']:
print(cood)
This will give you the nested coordinate list:
Feature coods:
[[-80.1135430232235, 33.49892053365546], [-80.1165139377003, 33.499835530094444], [-80.1170369402652, 33.49992051898103], [-80.11707393820328, 33.49992653060032]]
Feature coods:
[[-79.62281482439842, 33.289964520159124], [-79.62376378105404, 33.29028749972797], [-79.6247927817771, 33.29068750016911], [-79.62604278223982, 33.29121650014533]]
features is a list so you have to reference that by integer index [0]
for x in data['features'][0]['geometry']['coordinates']:
print(x)
for x in data['features'][0]['properties']:
print(x)

Update values in geojson file in Python

I have geojson file as follows:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[
57.45849609375,
57.36801461845934
],
[
57.10693359375,
56.31044317134597
],
[
59.205322265625,
56.20059291588374
],
[
59.4140625,
57.29091812634045
],
[
57.55737304687501,
57.36801461845934
]
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[
59.40307617187499,
57.29685437021898
],
[
60.8203125,
57.314657355733274
],
[
60.74340820312499,
56.26776108757582
],
[
59.227294921875,
56.21281407174654
],
[
59.447021484375,
57.29091812634045
]
]
}
}
]
}
I want to replace LineString in "type": "LineString" with Polygon, and also, replace coordinates last point of each linestring by coordinates of first point to make it close if it has more than 3 points.
How can I do it in Python with geopandas or pandas? Thanks.
Here is expected output:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
57.45849609375,
57.36801461845934
],
[
57.10693359375,
56.31044317134597
],
[
59.205322265625,
56.20059291588374
],
[
59.4140625,
57.29091812634045
],
[
57.45849609375,
57.36801461845934
]
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
59.40307617187499,
57.29685437021898
],
[
60.8203125,
57.314657355733274
],
[
60.74340820312499,
56.26776108757582
],
[
59.227294921875,
56.21281407174654
],
[
59.40307617187499,
57.29685437021898
]
]
}
}
]
}
Script to get type and coordinates of first LineString:
import json
from pprint import pprint
with open('data.geojson') as f:
data = json.load(f)
pprint(data)
data["features"][0]["geometry"]['type']
data["features"][0]["geometry"]['coordinates']
You can achieve that with the json module:
file_line = 'file.json'
file_poly = 'file_poly.json'
import json
with open(file_line, 'r') as f:
data = json.load(f)
for feature in data['features']:
if (feature['geometry']['type'] == 'LineString') & (len(feature['geometry']['coordinates']) >= 3):
feature['geometry']['type'] = 'Polygon'
feature['geometry']['coordinates'].append(feature['geometry']['coordinates'][0])
with open(file_poly, 'w+') as f:
json.dump(data, f, indent=2)

Categories

Resources