Skip to content

Commit

Permalink
geojson simple parser
Browse files Browse the repository at this point in the history
  • Loading branch information
HowcanoeWang committed Jan 5, 2024
1 parent 30297f9 commit 46aeea5
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 0 deletions.
2 changes: 2 additions & 0 deletions easyidp/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,8 @@ def __init__(self, data_dir, test_out):
self.labelme_warn = data_dir / "json_test" / "labelme_warn_img.json"
self.labelme_err = data_dir / "json_test" / "for_read_json.json"

self.geojson_soy = data_dir / "json_test" / "2023_soybean_field.geojson"

if isinstance(test_out, str):
test_out = Path(test_out)
self.out = test_out / "json_test"
Expand Down
192 changes: 192 additions & 0 deletions easyidp/jsonfile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import json
import os
import numpy as np
import geojson
import pyproj

from rich.table import Table
from rich.box import HORIZONTALS
from rich import print

class MyEncoder(json.JSONEncoder):
# The original json package doesn't compatible to numpy object, add this compatible encoder to it.
Expand Down Expand Up @@ -54,6 +60,192 @@ def read_json(json_path):
return data
else:
raise FileNotFoundError(f"Could not locate the given json file [{json_path}]")

def _check_geojson_format(geojson_path):
if '.geojson' not in str(geojson_path):
raise TypeError(f"The input file format should be *.geojson, not [.{str(geojson_path).split('.')[-1]}]")

with open(geojson_path, 'r') as f:
geojson_data = geojson.load(f)

# Only the FeatureCollection geojson type is supported.
if geojson_data.type != 'FeatureCollection':
raise TypeError(f'A `FeatureCollection` type geojson is expected, current type is `{geojson_data.type}`')

if 'features' not in geojson_data.keys():
raise TypeError('geojson does not have features properties')

return geojson_data

def read_geojson(geojson_path):
"""Read geojson file to python dict
Parameters
----------
geojson_path : str
The path to geojson file
Returns
-------
dict
.. code-block:: python
{
'crs': pyproj.CRS,
'geometry': [
{'coordiante': [[[...]]], 'type': '...'},
...
],
'property': [
{key1: v1, key2: v2},
...
]
}
Example
-------
Data prepare:
.. code-block:: python
>>> import easyidp as idp
>>> test_data = idp.data.TestData()
Use this function:
.. code-block:: python
>>> out = idp.jsonfile.read_json(test_data.json.geojson_soy)
>>> out
{'crs': <Projected CRS: EPSG:6677>
Name: JGD2011 / Japan Plane Rectangular CS IX
Axis Info [cartesian]:
- X[north]: Northing (metre)
- Y[east]: Easting (metre)
Area of Use:
- name: Japan - onshore - Honshu - Tokyo-to.
- bounds: (138.4, 29.31, 141.11, 37.98)
Coordinate Operation:
- name: Japan Plane Rectangular CS zone IX
- method: Transverse Mercator
Datum: Japanese Geodetic Datum 2011
- Ellipsoid: GRS 1980
- Prime Meridian: Greenwich,
'geometry': [
{
"coordinates": [[[-26384.952573, -28870.678514], ..., [-26384.952573, -28870.678514]]],
"type": "Polygon"
},
...
],
'property': [
{'FID': 65,
'試験区': 'SubBlk 2b',
'ID': 0,
'除草剤': '有',
'plotName': 'Enrei-10',
'lineNum': 1},
...
]
}
"""
out_dict = {'crs': None, 'geometry': [], 'property':[]}

geojson_data = _check_geojson_format(geojson_path)

if 'crs' in geojson_data.keys():
# ensure it has correct format for CRS info
if 'properties' in geojson_data.crs and \
'name' in geojson_data.crs['properties']:
out_dict['crs'] = pyproj.CRS.from_string(geojson_data.crs['properties']['name'])
else:
crs_template = {
"type": "name",
"properties": {
"name": "EPSG:xxxx"
}
}
print(f'geojson does not have standard CRS properties like:\n{crs_template}'
f'\nBut the following is obtained:\n{geojson_data.crs}')
else:
print(f'[json][geojson] geojson does not have CRS properties')

for f in geojson_data.features:
out_dict['geometry'].append(f['geometry'])
out_dict['property'].append(f['properties'])

return out_dict

def show_geojson_fields(geojson_path):
"""
Show geojson properties data, for better setting ``name_field`` of :py:obj:`read_geojson <easyidp.roi.ROI.read_geojson>`
Parameters
----------
geojson_path : : str
the file path of \*.geojson
Example
-------
.. code-block:: python
>>> import easyidp as idp
>>> test_data = idp.data.TestData()
>>> idp.jsonfile.show_geojson_fields(test_data.json.geojson_soy)
Properties of /Users/hwang/Library/Application
Support/easyidp.data/data_for_tests/json_test/2023_soybean_field.geojson
──────────────────────────────────────────────────────────────────────────────────
[-1] [0] FID [1] 試験区 [2] ID [3] 除草剤 [4] plotName [5] lineNum
──────────────────────────────────────────────────────────────────────────────────
0 65 SubBlk 2b 0 有 Enrei-10 1
1 97 SubBlk 2b 0 有 Enrei-20 1
2 147 SubBlk 2b 0 有 Nakasenri-10 1
... ... ... ... ... ... ...
257 259 SB 0a 0 Tachinagaha-10 3
258 4 SB 0a 0 Fukuyutaka-10 3
259 1 SubBlk 2a 0 無 Enrei-20 1
──────────────────────────────────────────────────────────────────────────────────
"""
geojson_data = _check_geojson_format(geojson_path)

table = Table(title=f"Properties of {geojson_path}", box=HORIZONTALS)

table.add_column("[-1]", justify='right')
for i, c in enumerate(geojson_data.features[0]['properties'].keys()):
table.add_column(f"[{i}] {c}", justify='center')

row_num = len(geojson_data.features)
col_num = len(geojson_data.features[0]['properties'].keys())

if row_num >= 6:
show_idx_top = [0, 1, 2]
show_idx_btm = [-3, -2, -1]

for i in show_idx_top:
row = [str(i)] + [str(k) for k in geojson_data.features[i]['properties'].values()]
table.add_row(*row)

omit_row = ['...'] * (col_num + 1)
table.add_row(*omit_row)

for i in show_idx_btm:
row = [str(row_num + i)] + [str(k) for k in geojson_data.features[i]['properties'].values()]
table.add_row(*row)

else:
# print all without omit
show_idx = list(range(row_num))

for i in show_idx:
row = [str(i)] + list(geojson_data.features[i]['properties'].values())
table.add_row(*row)

print(table)


def dict2json(data_dict, json_path, indent=None, encoding='utf-8'):
"""Save dict object to the same structure json file
Expand Down
36 changes: 36 additions & 0 deletions tests/test_jsonfile.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import re
import pytest
import pyproj
import numpy as np
from pathlib import Path
import easyidp as idp
Expand Down Expand Up @@ -38,3 +39,38 @@ def test_read_dict():
out = idp.jsonfile.read_json(test_data.json.for_read_json)

assert out == {"test": {"rua": [[12, 34], [45, 56]]}, "hua": [34, 34.567]}

def test_read_geojson_wrong_format():
with pytest.raises(TypeError, match=re.escape("The input file format should be *.geojson, not [.json]")):
# input a *.json file
out = idp.jsonfile.read_geojson(test_data.json.labelme_demo)

def test_read_geojson():
out = idp.jsonfile.read_geojson(test_data.json.geojson_soy)

assert out['crs'] == pyproj.CRS.from_epsg(6677)

assert len(out['geometry']) == len(out['property'])

geojson_table_preview_unix = \
" Properties of /Users/hwang/Library/Application \n"\
" Support/easyidp.data/data_for_tests/json_test/2023_soybean_field.geojson \n"\
" ────────────────────────────────────────────────────────────────────────────────── \n"\
" [-1] [0] FID [1] 試験区 [2] ID [3] 除草剤 [4] plotName [5] lineNum \n"\
" ────────────────────────────────────────────────────────────────────────────────── \n"\
" 0 65 SubBlk 2b 0 有 Enrei-10 1 \n"\
" 1 97 SubBlk 2b 0 有 Enrei-20 1 \n"\
" 2 147 SubBlk 2b 0 有 Nakasenri-10 1 \n"\
" ... ... ... ... ... ... ... \n"\
" 257 259 SB 0a 0 Tachinagaha-10 3 \n"\
" 258 4 SB 0a 0 Fukuyutaka-10 3 \n"\
" 259 1 SubBlk 2a 0 無 Enrei-20 1 \n"\
" ────────────────────────────────────────────────────────────────────────────────── \n"
def test_show_geojson_fields(capfd):

idp.jsonfile.show_geojson_fields(test_data.json.geojson_soy)

out, err = capfd.readouterr()

# just ensure the function can run, no need to guarantee the same string outputs
assert "Properties of" in out

0 comments on commit 46aeea5

Please sign in to comment.