Skip to content

Commit b4548f2

Browse files
committed
feat: Add JSON I/O functionality for surface points
- Add JSON I/O module with schema definitions - Implement surface points loading functionality - Add comprehensive test suite for surface points I/O - Create tutorial demonstrating JSON I/O usage
1 parent f495564 commit b4548f2

File tree

6 files changed

+492
-0
lines changed

6 files changed

+492
-0
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""
2+
Tutorial for JSON I/O operations in GemPy - Surface Points
3+
=======================================================
4+
5+
This tutorial demonstrates how to save and load surface points data using JSON files in GemPy.
6+
"""
7+
8+
import json
9+
import numpy as np
10+
import gempy as gp
11+
from gempy.modules.json_io import JsonIO
12+
13+
# Create a sample surface points dataset
14+
# ------------------------------------
15+
16+
# Create some sample surface points
17+
x = np.array([0, 1, 2, 3, 4])
18+
y = np.array([0, 1, 2, 3, 4])
19+
z = np.array([0, 1, 2, 3, 4])
20+
ids = np.array([0, 0, 1, 1, 2]) # Three different surfaces
21+
nugget = np.array([0.00002, 0.00002, 0.00002, 0.00002, 0.00002])
22+
23+
# Create name to id mapping
24+
name_id_map = {f"surface_{id}": id for id in np.unique(ids)}
25+
26+
# Create a SurfacePointsTable
27+
surface_points = gp.data.SurfacePointsTable.from_arrays(
28+
x=x,
29+
y=y,
30+
z=z,
31+
names=[f"surface_{id}" for id in ids],
32+
nugget=nugget,
33+
name_id_map=name_id_map
34+
)
35+
36+
# Create a JSON file with the surface points data
37+
# ---------------------------------------------
38+
39+
# Create the JSON structure
40+
json_data = {
41+
"metadata": {
42+
"name": "sample_model",
43+
"creation_date": "2024-03-19",
44+
"last_modification_date": "2024-03-19",
45+
"owner": "tutorial"
46+
},
47+
"surface_points": [
48+
{
49+
"x": float(x[i]),
50+
"y": float(y[i]),
51+
"z": float(z[i]),
52+
"id": int(ids[i]),
53+
"nugget": float(nugget[i])
54+
}
55+
for i in range(len(x))
56+
],
57+
"orientations": [],
58+
"faults": [],
59+
"series": [],
60+
"grid_settings": {
61+
"regular_grid_resolution": [10, 10, 10],
62+
"regular_grid_extent": [0, 4, 0, 4, 0, 4],
63+
"octree_levels": None
64+
},
65+
"interpolation_options": {}
66+
}
67+
68+
# Save the JSON file
69+
with open("sample_surface_points.json", "w") as f:
70+
json.dump(json_data, f, indent=4)
71+
72+
# Load the surface points from JSON
73+
# -------------------------------
74+
75+
# Load the model from JSON
76+
loaded_surface_points = JsonIO._load_surface_points(json_data["surface_points"])
77+
78+
# Verify the loaded data
79+
print("\nOriginal surface points:")
80+
print(surface_points)
81+
print("\nLoaded surface points:")
82+
print(loaded_surface_points)
83+
84+
# Verify the data matches
85+
print("\nVerifying data matches:")
86+
print(f"X coordinates match: {np.allclose(surface_points.xyz[:, 0], loaded_surface_points.xyz[:, 0])}")
87+
print(f"Y coordinates match: {np.allclose(surface_points.xyz[:, 1], loaded_surface_points.xyz[:, 1])}")
88+
print(f"Z coordinates match: {np.allclose(surface_points.xyz[:, 2], loaded_surface_points.xyz[:, 2])}")
89+
print(f"IDs match: {np.array_equal(surface_points.ids, loaded_surface_points.ids)}")
90+
print(f"Nugget values match: {np.allclose(surface_points.nugget, loaded_surface_points.nugget)}")
91+
92+
# Print the name_id_maps to compare
93+
print("\nName to ID mappings:")
94+
print(f"Original: {surface_points.name_id_map}")
95+
print(f"Loaded: {loaded_surface_points.name_id_map}")
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"metadata": {
3+
"name": "sample_model",
4+
"creation_date": "2024-03-19",
5+
"last_modification_date": "2024-03-19",
6+
"owner": "tutorial"
7+
},
8+
"surface_points": [
9+
{
10+
"x": 0.0,
11+
"y": 0.0,
12+
"z": 0.0,
13+
"id": 0,
14+
"nugget": 2e-05
15+
},
16+
{
17+
"x": 1.0,
18+
"y": 1.0,
19+
"z": 1.0,
20+
"id": 0,
21+
"nugget": 2e-05
22+
},
23+
{
24+
"x": 2.0,
25+
"y": 2.0,
26+
"z": 2.0,
27+
"id": 1,
28+
"nugget": 2e-05
29+
},
30+
{
31+
"x": 3.0,
32+
"y": 3.0,
33+
"z": 3.0,
34+
"id": 1,
35+
"nugget": 2e-05
36+
},
37+
{
38+
"x": 4.0,
39+
"y": 4.0,
40+
"z": 4.0,
41+
"id": 2,
42+
"nugget": 2e-05
43+
}
44+
],
45+
"orientations": [],
46+
"faults": [],
47+
"series": [],
48+
"grid_settings": {
49+
"regular_grid_resolution": [
50+
10,
51+
10,
52+
10
53+
],
54+
"regular_grid_extent": [
55+
0,
56+
4,
57+
0,
58+
4,
59+
0,
60+
4
61+
],
62+
"octree_levels": null
63+
},
64+
"interpolation_options": {}
65+
}

gempy/modules/json_io/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""
2+
JSON I/O module for GemPy.
3+
This module provides functionality to load and save GemPy models to/from JSON files.
4+
"""
5+
6+
from .json_operations import JsonIO
7+
8+
__all__ = ['JsonIO']
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""
2+
Module for JSON I/O operations in GemPy.
3+
This module provides functionality to load and save GemPy models to/from JSON files.
4+
"""
5+
6+
import json
7+
from typing import Dict, Any, Optional, List
8+
import numpy as np
9+
10+
from gempy.core.data.surface_points import SurfacePointsTable
11+
from gempy.core.data.orientations import OrientationsTable
12+
from gempy.core.data.structural_frame import StructuralFrame
13+
from gempy.core.data.grid import Grid
14+
from gempy.core.data.geo_model import GeoModel
15+
from .schema import SurfacePoint, GemPyModelJson
16+
17+
18+
class JsonIO:
19+
"""Class for handling JSON I/O operations for GemPy models."""
20+
21+
@staticmethod
22+
def load_model_from_json(file_path: str) -> GeoModel:
23+
"""
24+
Load a GemPy model from a JSON file.
25+
26+
Args:
27+
file_path (str): Path to the JSON file
28+
29+
Returns:
30+
GeoModel: A new GemPy model instance
31+
"""
32+
with open(file_path, 'r') as f:
33+
data = json.load(f)
34+
35+
# Validate the JSON data against our schema
36+
if not JsonIO._validate_json_schema(data):
37+
raise ValueError("Invalid JSON schema")
38+
39+
# Load surface points
40+
surface_points = JsonIO._load_surface_points(data['surface_points'])
41+
42+
# TODO: Load other components
43+
raise NotImplementedError("Only surface points loading is implemented")
44+
45+
@staticmethod
46+
def _load_surface_points(surface_points_data: List[SurfacePoint]) -> SurfacePointsTable:
47+
"""
48+
Load surface points from JSON data.
49+
50+
Args:
51+
surface_points_data (List[SurfacePoint]): List of surface point dictionaries
52+
53+
Returns:
54+
SurfacePointsTable: A new SurfacePointsTable instance
55+
56+
Raises:
57+
ValueError: If the data is invalid or missing required fields
58+
"""
59+
# Validate data structure
60+
required_fields = {'x', 'y', 'z', 'id', 'nugget'}
61+
for i, sp in enumerate(surface_points_data):
62+
missing_fields = required_fields - set(sp.keys())
63+
if missing_fields:
64+
raise ValueError(f"Missing required fields in surface point {i}: {missing_fields}")
65+
66+
# Validate data types
67+
if not all(isinstance(sp[field], (int, float)) for field in ['x', 'y', 'z', 'nugget']):
68+
raise ValueError(f"Invalid data type in surface point {i}. All coordinates and nugget must be numeric.")
69+
if not isinstance(sp['id'], int):
70+
raise ValueError(f"Invalid data type in surface point {i}. ID must be an integer.")
71+
72+
# Extract coordinates and other data
73+
x = np.array([sp['x'] for sp in surface_points_data])
74+
y = np.array([sp['y'] for sp in surface_points_data])
75+
z = np.array([sp['z'] for sp in surface_points_data])
76+
ids = np.array([sp['id'] for sp in surface_points_data])
77+
nugget = np.array([sp['nugget'] for sp in surface_points_data])
78+
79+
# Create name_id_map from unique IDs
80+
unique_ids = np.unique(ids)
81+
name_id_map = {f"surface_{id}": id for id in unique_ids}
82+
83+
# Create SurfacePointsTable
84+
return SurfacePointsTable.from_arrays(
85+
x=x,
86+
y=y,
87+
z=z,
88+
names=[f"surface_{id}" for id in ids],
89+
nugget=nugget,
90+
name_id_map=name_id_map
91+
)
92+
93+
@staticmethod
94+
def save_model_to_json(model: GeoModel, file_path: str) -> None:
95+
"""
96+
Save a GemPy model to a JSON file.
97+
98+
Args:
99+
model (GeoModel): The GemPy model to save
100+
file_path (str): Path where to save the JSON file
101+
"""
102+
# TODO: Implement saving logic
103+
raise NotImplementedError("JSON saving not yet implemented")
104+
105+
@staticmethod
106+
def _validate_json_schema(data: Dict[str, Any]) -> bool:
107+
"""
108+
Validate the JSON data against the expected schema.
109+
110+
Args:
111+
data (Dict[str, Any]): The JSON data to validate
112+
113+
Returns:
114+
bool: True if valid, False otherwise
115+
"""
116+
# Check required top-level keys
117+
required_keys = {'metadata', 'surface_points', 'orientations', 'faults',
118+
'series', 'grid_settings', 'interpolation_options'}
119+
if not all(key in data for key in required_keys):
120+
return False
121+
122+
# Validate surface points
123+
if not isinstance(data['surface_points'], list):
124+
return False
125+
126+
for sp in data['surface_points']:
127+
required_sp_keys = {'x', 'y', 'z', 'id', 'nugget'}
128+
if not all(key in sp for key in required_sp_keys):
129+
return False
130+
if not all(isinstance(sp[key], (int, float)) for key in ['x', 'y', 'z', 'nugget']):
131+
return False
132+
if not isinstance(sp['id'], int):
133+
return False
134+
135+
return True

gempy/modules/json_io/schema.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Schema definitions for JSON I/O operations in GemPy.
3+
This module defines the expected structure of JSON files for loading and saving GemPy models.
4+
"""
5+
6+
from typing import TypedDict, List, Dict, Any, Optional
7+
8+
class SurfacePoint(TypedDict):
9+
x: float
10+
y: float
11+
z: float
12+
id: int
13+
nugget: float
14+
15+
class Orientation(TypedDict):
16+
x: float
17+
y: float
18+
z: float
19+
G_x: float
20+
G_y: float
21+
G_z: float
22+
id: int
23+
polarity: int
24+
25+
class Fault(TypedDict):
26+
name: str
27+
id: int
28+
is_active: bool
29+
30+
class Series(TypedDict):
31+
name: str
32+
id: int
33+
is_active: bool
34+
is_fault: bool
35+
order_series: int
36+
37+
class GridSettings(TypedDict):
38+
regular_grid_resolution: List[int]
39+
regular_grid_extent: List[float]
40+
octree_levels: Optional[int]
41+
42+
class ModelMetadata(TypedDict):
43+
name: str
44+
creation_date: str
45+
last_modification_date: str
46+
owner: str
47+
48+
class GemPyModelJson(TypedDict):
49+
metadata: ModelMetadata
50+
surface_points: List[SurfacePoint]
51+
orientations: List[Orientation]
52+
faults: List[Fault]
53+
series: List[Series]
54+
grid_settings: GridSettings
55+
interpolation_options: Dict[str, Any]

0 commit comments

Comments
 (0)