Replace multiple keys and values of JSON file in Python - python

For geojson type file named data as follows:
{
"type": "FeatureCollection",
"name": "entities",
"features": [{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbPolyline",
"EntityHandle": "1A0"
},
"geometry": {
"type": "LineString",
"coordinates": [
[3220.136443006845184, 3001.530372177397112],
[3847.34171007254281, 3000.86074447018018],
[3847.34171007254281, 2785.240077064262096],
[3260.34191304818205, 2785.240077064262096],
[3260.34191304818205, 2795.954148466309107]
]
}
},
{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbPolyline",
"EntityHandle": "1A4"
},
"geometry": {
"type": "LineString",
"coordinates": [
[3611.469650131302842, 2846.845982610575902],
[3695.231030111376185, 2846.845982610575902],
[3695.231030111376185, 2785.240077064262096],
[3611.469650131302842, 2785.240077064262096],
[3611.469650131302842, 2846.845982610575902]
]
}
}
]
}
I hope to realize the following manipulation to data:
replace key EntityHandle with Name;
replace EntityHandle's value from hex number with 'sf_001', 'sf_002', 'sf_003', etc;
replace type's value LineString with Polygon;
replace coordinates's two square brackets with three square brackets.
This is expected output:
{
"type": "FeatureCollection",
"name": "entities",
"features": [{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbPolyline",
"Name": "sf_001"
},
"geometry": {
"type": "Polygon",
"coordinates": [ [
[3220.136443006845184, 3001.530372177397112],
[3847.34171007254281, 3000.86074447018018],
[3847.34171007254281, 2785.240077064262096],
[3260.34191304818205, 2785.240077064262096],
[3260.34191304818205, 2795.954148466309107]
] ]
}
},
{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbPolyline",
"Name": "sf_002"
},
"geometry": {
"type": "Polygon",
"coordinates": [ [
[3611.469650131302842, 2846.845982610575902],
[3695.231030111376185, 2846.845982610575902],
[3695.231030111376185, 2785.240077064262096],
[3611.469650131302842, 2785.240077064262096],
[3611.469650131302842, 2846.845982610575902]
] ]
}
}
]
}
I'm new in JSON file manipulation using Python. Please help me, thanks at advance.
import json
from pprint import pprint
with open('data.geojson') as f:
data = json.load(f)
pprint(data)
for feature in data['features']:
#print(feature)
print(feature['properties']['EntityHandle'])
for feature in data['features']:
feature['properties']['EntityHandle'] = feature['properties']['Name'] #Rename `EntityHandle` to `Name`
del feature['properties']['EntityHandle']
Result:
Traceback (most recent call last):
File "<ipython-input-79-8b30b71fedf9>", line 2, in <module>
feature['properties']['EntityHandle'] = feature['properties']['Name']
KeyError: 'Name'

Give this a shot:
You can rename a key by using:
dict['NewKey'] = dict.pop('OldKey')
And to replace a value, it's just as simple as setting the new vale equal to the specific key:
dict['Key'] = new_value
Full Code:
data = {
"type": "FeatureCollection",
"name": "entities",
"features": [{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbPolyline",
"EntityHandle": "1A0"
},
"geometry": {
"type": "LineString",
"coordinates": [
[3220.136443006845184, 3001.530372177397112],
[3847.34171007254281, 3000.86074447018018],
[3847.34171007254281, 2785.240077064262096],
[3260.34191304818205, 2785.240077064262096],
[3260.34191304818205, 2795.954148466309107]
]
}
},
{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbPolyline",
"EntityHandle": "1A4"
},
"geometry": {
"type": "LineString",
"coordinates": [
[3611.469650131302842, 2846.845982610575902],
[3695.231030111376185, 2846.845982610575902],
[3695.231030111376185, 2785.240077064262096],
[3611.469650131302842, 2785.240077064262096],
[3611.469650131302842, 2846.845982610575902]
]
}
}
]
}
sf_count = 0
for feature in data['features']:
feature['properties']['Name'] = feature['properties'].pop('EntityHandle') #Rename `EntityHandle` to `Name`
sf_count += 1
feature['properties']['Name'] = 'sf_%.3d' %sf_count # Replace hex with incrimental sf_xxx
feature['geometry']['type'] = 'Polygon' # Replace `LineString` to `Polygon`
feature['geometry']['coordinates'] = [[ each for each in feature['geometry']['coordinates'] ]]

Related

Change start, end lineString coordinate order in geoJson file

I have a .geojson file with many lineStrings with the location of transects used to monitor shoreline change, so each transect runs across a land/water boundary. When visualized as is, the transects originate offshore and end onshore. For an analysis tool I am using, I need the locations to be swapped: the first coordinate needs to start on land and end offshore. I will have many thousands of these transects to change and want to make sure I'm doing it correctly but can't seem to figure out this very simple task (sorry, I am new here). I am working in python and earth engine.
# original
{
"type": "FeatureCollection",
"name": "EastChukci_small_testArea",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::3857" } },
"features": [
{ "type": "Feature", "properties": { "name": 2722 }, "geometry": { "type": "LineString", "coordinates": [ [ -17592698.71288351342082, 11344741.029055444523692 ], [ -17592054.347651835530996, 11343198.733621645718813 ] ] } },
{ "type": "Feature", "properties": { "name": 2723 }, "geometry": { "type": "LineString", "coordinates": [ [ -17592838.831736516207457, 11344682.393273767083883 ], [ -17592194.440066188573837, 11343140.124529516324401 ] ] } },
{ "type": "Feature", "properties": { "name": 2724 }, "geometry": { "type": "LineString", "coordinates": [ [ -17592978.948162343353033, 11344623.755085829645395 ], [ -17592334.530055023729801, 11343081.513031836599112 ] ] } },
]
}
# desired
{
"type": "FeatureCollection",
"name": "EastChukci_small_testArea",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::3857" } },
"features": [
{ "type": "Feature", "properties": { "name": 2722 }, "geometry": { "type": "LineString", "coordinates": [[ -17592054.347651835530996, 11343198.733621645718813 ], [ -17592698.71288351342082, 11344741.029055444523692 ] ] } },
{ "type": "Feature", "properties": { "name": 2723 }, "geometry": { "type": "LineString", "coordinates": [ [ -17592194.440066188573837, 11343140.124529516324401 ], [ -17592838.831736516207457, 11344682.393273767083883 ] ] } },
{ "type": "Feature", "properties": { "name": 2724 }, "geometry": { "type": "LineString", "coordinates": [ [ -17592334.530055023729801, 11343081.513031836599112 ] ], [ -17592978.948162343353033, 11344623.755085829645395 ] ] } },
]
}
Thanks in advance.
To read and write a json file you can use json module. The code below should solve your problem, but the downside of it is that it loads the whole file to the memory at once.
import json
with open('data.json', 'r') as json_file:
data = json.load(json_file)
for feature_data in data['features']:
feature_data['geometry']['coordinates'].reverse()
with open('data.json', 'w') as json_file:
json.dump(data, json_file)

How to position attribute in geojson on top of all properties?

I am trying to remove feature_id property from properties array and move upwards.
with open('test.geojson', 'r+') as gjson:
data = json.load(gjson)
for l in range(0, len(data['features'])):
data['features'][l]['id'] = data['features'][l]['properties']['feature_id']
del data['features'][l]['properties']['feature_id']
gjson.seek(0)
json.dump(data, gjson, indent=4)
gjson.truncate()
This is the input.
{
"type": "FeatureCollection",
"name": "name",
"features": [
{
"type": "Feature",
"properties": {
"feature_id": "1e181120-2047-4f97-a359-942ef5940da1",
"type": 1
},
"geometry": {
"type": "Polygon",
"coordinates": [
[...]
]
}
}
]
}
It does the job but adds the property at the bottom
{
"type": "FeatureCollection",
"name": "name",
"features": [
{
"type": "Feature",
"properties": {
"type": 1
},
"geometry": {
"type": "Polygon",
"coordinates": [
[..]
]
},
"id": "1e181120-2047-4f97-a359-942ef5940da1"
}
]
}
As you can see id is added at last but it should be on top before properties.
You can use OrderedDict for that.
with open('test.geojson', 'r+') as gjson:
data = json.load(gjson, object_pairs_hook=OrderedDict)
for l in range(0, len(data['features'])):
d = data['features'][l]
d['id'] = data['features'][l]['properties']['feature_id']
d.move_to_end('id', last=False)
del d['properties']['feature_id']
gjson.seek(0)
json.dump(data, gjson, indent=4)
gjson.truncate()

JSON compatibility with Mapbox Studio

I am trying to use a JSON file in Mapbox studio for network analysis but it gives me an error:
Input failed. "type" member required on line 1.
The representative sample of JSON file is:
"version": 0.6,
"generator": "Overpass API 0.7.56.2 b688b00f",
"osm3s": {
"timestamp_osm_base": "2020-03-27T11:58:01Z",
"copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."
},
"elements": [
{
"type": "node",
"id": 123458059,
"lat": -38.3344495,
"lon": 143.5394486
},
{
"type": "node",
"id": 123458066,
"lat": -38.3394461,
"lon": 143.5923655,
"tags": {
"crossing": "traffic_signals",
"highway": "traffic_signals"
}
},
{
"type": "way",
"id": 769574290,
"nodes": [
7183581936,
681081177,
1561328098,
1539139562,
448021781
],
"tags": {
"highway": "trunk",
"lanes": "2",
"maxspeed": "80",
"name": "Princes Highway",
"ref": "A1",
"source:maxspeed:sign": "mapillary"
}
},
{
"type": "way",
"id": 776227225,
"nodes": [
1017428185,
317738200
],
"tags": {
"alt_name": "Princes Highway",
"highway": "trunk",
"lanes": "2",
"maxspeed": "50",
"name": "Murray Street",
"ref": "A1",
"source:maxspeed:sign": "OpenStreetCam",
"source:name": "services.land.vic.gov.au"
}
}
]
}
Does the error occur because of the specification of the format? Do we need to reformat the features or types?
To upload data to Mapbox you will need to convert your JSON file to GeoJSON, a subset of the JSON format. For example:
{"type": "FeatureCollection",
"features": [
{
"geometry": {
"type": "Point",
"coordinates": [
-76.9750541388,
38.8410857803
]
},
"type": "Feature",
"properties": {
"description": "Southern Ave",
"marker-symbol": "rail-metro",
"title": "Southern Ave",
"url": "http://www.wmata.com/rider_tools/pids/showpid.cfm?station_id=107",
"lines": [
"Green"
],
"address": "1411 Southern Avenue, Temple Hills, MD 20748"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [
-76.935256783,
38.9081784965
]
},
"type": "Feature",
"properties": {
"description": "Deanwood",
"marker-symbol": "rail-metro",
"title": "Deanwood",
"url": "http://www.wmata.com/rider_tools/pids/showpid.cfm?station_id=65",
"lines": [
"Orange"
],
"address": "4720 Minnesota Avenue NE, Washington, DC 20019"
}
}
]}
Mapbox accepts GeoJSON, you'll need to ask Overpass to return data as GeoJSON.

Combine part of geojson object into another in Python

EDIT: I am trying to manipulate JSON files in Python. In my data some polygons have multiple related information: coordinates (LineString) and area percent and area (Text and Area in Point), I want to combine them to a single JSON object. As an example, the data from files are as follows:
data = {
"type": "FeatureCollection",
"name": "entities",
"features": [{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbBlockReference",
"EntityHandle": "2F1"
},
"geometry": {
"type": "LineString",
"coordinates": [
[61.971069681118479, 36.504485105673659],
[46.471068755199667, 36.504485105673659],
[46.471068755199667, 35.954489281866685],
[44.371068755199758, 35.954489281866685],
[44.371068755199758, 36.10448936390457],
[43.371069617387093, 36.104489150107824],
[43.371069617387093, 23.904496401184584],
[48.172716774891342, 23.904496401184584],
[48.171892994728751, 17.404489374370311],
[61.17106949647404, 17.404489281863786],
[61.17106949647404, 19.404489281863786],
[61.971069689453991, 19.404489282256687],
[61.971069681118479, 36.504485105673659]
]
}
},
{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbMText",
"EntityHandle": "2F1",
"Text": "6%"
},
"geometry": {
"type": "Point",
"coordinates": [49.745686139884583, 28.11445704760262, 0.0]
}
},
{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbMText",
"EntityHandle": "2F1",
"Area": "100"
},
"geometry": {
"type": "Point",
"coordinates": [50.216857362443989, 63.981197759829229, 0.0]
}
},
{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbBlockReference",
"EntityHandle": "2F7"
},
"geometry": {
"type": "LineString",
"coordinates": [
[62.37106968111857, 36.504489398648715],
[62.371069689452725, 19.404489281863786],
[63.171069496474047, 19.404489281863786],
[63.171069496474047, 17.404489281863786],
[77.921070051947027, 17.404489281863786],
[77.921070051947027, 19.504489281855054],
[78.671070051947027, 19.504489281855054],
[78.671070051897914, 36.504485105717322],
[62.37106968111857, 36.504489398648715]
]
}
},
{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbMText",
"EntityHandle": "2F7",
"Text": "5.8%"
},
"geometry": {
"type": "Point",
"coordinates": [67.27548061311245, 28.11445704760262, 0.0]
}
}
]
}
I want to combine Point's Text and Area key and values to LineString based on EntityHandle's values, and also delete Point lines. The expected output is:
{
"type": "FeatureCollection",
"name": "entities",
"features": [{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbBlockReference",
"EntityHandle": "2F1",
"Text": "6%",
"Area": "100"
},
"geometry": {
"type": "LineString",
"coordinates": [
[61.971069681118479, 36.504485105673659],
[46.471068755199667, 36.504485105673659],
[46.471068755199667, 35.954489281866685],
[44.371068755199758, 35.954489281866685],
[44.371068755199758, 36.10448936390457],
[43.371069617387093, 36.104489150107824],
[43.371069617387093, 23.904496401184584],
[48.172716774891342, 23.904496401184584],
[48.171892994728751, 17.404489374370311],
[61.17106949647404, 17.404489281863786],
[61.17106949647404, 19.404489281863786],
[61.971069689453991, 19.404489282256687],
[61.971069681118479, 36.504485105673659]
]
}
},
{
"type": "Feature",
"properties": {
"Layer": "0",
"SubClasses": "AcDbEntity:AcDbBlockReference",
"EntityHandle": "2F7",
"Text": "5.8%"
},
"geometry": {
"type": "LineString",
"coordinates": [
[62.37106968111857, 36.504489398648715],
[62.371069689452725, 19.404489281863786],
[63.171069496474047, 19.404489281863786],
[63.171069496474047, 17.404489281863786],
[77.921070051947027, 17.404489281863786],
[77.921070051947027, 19.504489281855054],
[78.671070051947027, 19.504489281855054],
[78.671070051897914, 36.504485105717322],
[62.37106968111857, 36.504489398648715]
]
}
}
]
}
Is it possible to get result above in Python? Thanks.
Updated solution, thanks to #dodopy:
import json
features = data["features"]
point_handle_text = {
i["properties"]["EntityHandle"]: i["properties"]["Text"]
for i in features
if i["geometry"]["type"] == "Point"
}
point_handle_area = {
i["properties"]["EntityHandle"]: i["properties"]["Area"]
for i in features
if i["geometry"]["type"] == "Point"
}
combine_features = []
for i in features:
if i["geometry"]["type"] == "LineString":
i["properties"]["Text"] = point_handle_text.get(i["properties"]["EntityHandle"])
combine_features.append(i)
data["features"] = combine_features
combine_features = []
for i in features:
if i["geometry"]["type"] == "LineString":
i["properties"]["Area"] = point_handle_area.get(i["properties"]["EntityHandle"])
combine_features.append(i)
data["features"] = combine_features
with open('test.geojson', 'w+') as f:
json.dump(data, f, indent=2)
But I get an error:
Traceback (most recent call last):
File "<ipython-input-131-d132c8854a9c>", line 6, in <module>
for i in features
File "<ipython-input-131-d132c8854a9c>", line 7, in <dictcomp>
if i["geometry"]["type"] == "Point"
KeyError: 'Text'
example like this:
import json
data = json.loads(json_data)
features = data["features"]
point_handle_text = {
i["properties"]["EntityHandle"]: i["properties"]["Text"]
for i in features
if i["geometry"]["type"] == "Point"
}
combine_features = []
for i in features:
if i["geometry"]["type"] == "LineString":
i["properties"]["Text"] = point_handle_text.get(i["properties"]["EntityHandle"])
combine_features.append(i)
data["features"] = combine_features
json_data = json.dumps(data)
Yes, it is possible to get your result in python. It just requires storing the json data into a data structure we can work with in python and then writing an algorithm to combine features with the same entity type. I wrote up a script to do just that, along with comments. The program extracts the text property from the Point feature and places it into the properties of the LineString feature. Then, we essentially discard Point.
BTW, your 'before' json data has a trailing comma that shouldn't be there.
Using Python 3.7.0:
import json
import collections
def main():
with open('before_data.json') as f:
before_data = json.load(f) # makes a python dict from the json file and stores in before
features = before_data['features'] # list of features
# loop through features, construct dictionary of entity handle mapped to point texts
point_entities = collections.defaultdict() # to avoid 'if key not in' pattern
for feature in features:
entity_handle = feature['properties']['EntityHandle']
# only append points
if feature['geometry']['type'] == 'Point':
point_entities[entity_handle] = feature['properties']['Text']
merged_features = []
for feature in features:
if feature['geometry']['type'] == 'LineString':
entity_handle = feature['properties']['EntityHandle']
text_percent = point_entities[entity_handle]
feature['properties']['Text'] = text_percent
merged_features.append(feature)
# print(json.dumps(before_data, indent=4))
result = before_data
result['features'] = merged_features
# compare with your expected output
with open('after_data.json') as f:
after_data = json.load(f)
print(result == after_data) # returns True
# finally, write your result to a file
with open('result.json', 'w') as output_file:
json.dump(result, output_file)
if __name__ == '__main__':
main()

Invalid GeoJson (Data was not JSON serializable)

I am fairly new to the geojson spec...and formatting is causing havok.
All I am trying to do is build a new "features" list (for Points only) which I am adding new 'properties' for. Then I write it into (test.json).
Right now I am returning which has intermittent cases of "type": "FeatureCollection" (I was only expecting to see it once - at the top of the file) and some bad } syntax errors:
{"type": "FeatureCollection",
"features": [
{"geometry": {
"type": "Point",
"coordinates": [-122.3447075, 47.6821492]},
"type": "Feature",
"properties": {
"marker-color": "#808080",
"timestamp": "2013-08-17T22:41:18Z",
"version": 3,
"user": "seattlefyi",
"last_updated": "over a year ago",
"id": 427307160,
"marker-size": "small"
}}, ## what??
]} ## what??
{"type": "FeatureCollection",
"features": [
{"geometry": {
"type": "Point",
"coordinates": [-122.3447075, 47.6821492]},
"type": "Feature",
"properties": {
"marker-color": "#808080",
"timestamp": "2013-08-17T22:41:18Z",
"version": 3,
"user": "seattlefyi",
"last_updated": "over a year ago",
"id": 427307160,
"marker-size": "small"
}}, ## what...no "type": "FeatureCollection" on this one?
{"geometry": {
"type": "Point",
"coordinates": [-122.377932, 47.5641566]},
"type": "Feature",
"properties": {
"marker-color": "#808080",
"timestamp": "2009-07-11T04:04:51Z",
"version": 1,
"user": "Rob Lanphier",
"last_updated": "over a year ago",
"id": 439976119,
"marker-size": "small"
}
}
]
}
However, I'm trying to return
{"type": "FeatureCollection",
"features": [
{"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-122.3447075, 47.6821492]},
"properties": {
"marker-color": "#808080",
"timestamp": "2013-08-17T22:41:18Z",
"version": 3,
"user": "ralph",
"last_updated": "over a year ago",
"id": 427307160,
"marker-size": "small"
}},
{"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-122.377932, 47.5641566]},
"properties": {
"marker-color": "#808080",
"timestamp": "2009-07-11T04:04:51Z",
"version": 1,
"user": "Rob Lanphier",
"last_updated": "over a year ago",
"id": 439976119,
"marker-size": "small"
}
}
]
}
Code is:
def write_to_features(source, class_time, color):
""" write the json into geojson
takes all the items from one node ("lat", "lon", "id", "user", "tags", "timestamp")
writes all the items with new tags "last_updated","marker-color","marker-size"
returns a dict
"""
pt = {
"type": "Feature",
"geometry": {
"type": 'Point',
"coordinates": [float(source['lon']), float(source['lat'])]
},
"properties": {
"user": source['user'],
"id": source['id'],
"version": source['version'],
"timestamp": source['timestamp'],
"last_updated": classified_time,
"marker-color": marker_color,
"marker-size": "small"
}
}
return pt
def __main__():
geojson = { "type": "FeatureCollection", "features": [] }
outfile = r'.\test.json'
with open(outfile, 'w') as geojson_file:
for item in all_data_dict['elements']:
point_dict = write_to_features(item, data_w_update, data_item_color)
geojson['features'].append(point_dict)
json.dump(geojson, geojson_file)
Shouldn't your json.dump(geojson, geojson_file) be outside your loop?you append in the line above it..so I'm questioning why would you dump/write to the file multiple times?I would think you should only be calling json.dump once.

Categories