Skip to content

Commit 8d84e90

Browse files
committed
[ENH] Added interface with Liquid Earth
1 parent 4406527 commit 8d84e90

File tree

5 files changed

+271
-1
lines changed

5 files changed

+271
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,6 @@ venv.bak/
7373
# Spyder project settings
7474
.spyderproject
7575
.spyproject
76+
77+
78+
.local_settings.json

subsurface/interfaces/liquid_earth/__init__.py

Whitespace-only changes.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import json
2+
import uuid
3+
from enum import Enum, auto
4+
5+
import requests
6+
7+
8+
class DataTypes(Enum):
9+
static_mesh = "static_mesh"
10+
collars = "wells/collars"
11+
cylinder = "wells/cylinders"
12+
volumes = "volumes"
13+
14+
15+
class LiquidEarthClient():
16+
token: str
17+
user_id: str
18+
19+
# Move to local settings
20+
host = "https://apim-liquidearth.azure-api.net/"
21+
22+
@property
23+
def header(self):
24+
header_dict = {
25+
"Authorization": f"Bearer {self.token}"
26+
}
27+
return header_dict
28+
29+
def login(self, username: str, password: str):
30+
end_point = "user/login_b2c"
31+
32+
data = {
33+
"username": username,
34+
"password": password,
35+
"grant_type": "password",
36+
"client_id": "685e08c0-0aac-42f6-80a9-c57440cd2962",
37+
"scope": "openid 685e08c0-0aac-42f6-80a9-c57440cd2962 offline_access"
38+
}
39+
40+
response = requests.post(self.host + end_point, data=data)
41+
if response.status_code == 400:
42+
raise Exception(f"Request failed: {response.text}")
43+
elif response.status_code == 200:
44+
_json = response.json()
45+
self.token = _json["access_token"]
46+
self.user_id = username
47+
48+
def get_available_projects(self):
49+
end_point = "cosmos-le/v1/available_projects/"
50+
response = requests.get(self.host + end_point, headers=self.header)
51+
52+
if response.status_code >= 400:
53+
raise Exception(f"Request failed: {response.text}")
54+
elif response.status_code >= 200:
55+
print(response.json())
56+
return response.json()
57+
58+
def add_new_project(self, project_name, extent, project_id=None):
59+
if project_id is None:
60+
project_id = str(uuid.uuid4())
61+
62+
available_projects: list = self.get_available_projects()
63+
64+
for project in available_projects:
65+
if project["project_id"] == project_id:
66+
raise ValueError("This Project already exists")
67+
68+
new_project = {
69+
"project_name": project_name,
70+
"project_id": project_id
71+
}
72+
available_projects.append(new_project)
73+
self._post_available_projects(available_projects)
74+
75+
new_project_meta = {
76+
"remote_name": project_name,
77+
"id": project_id,
78+
"owner": self.user_id,
79+
"extent": extent,
80+
"static_data_address": []
81+
}
82+
self._post_project_meta(new_project_meta)
83+
return project_id
84+
85+
def add_data_to_project(self, project_id: str, data_name: str, data_type: DataTypes,
86+
header: json, body: bytearray):
87+
88+
self._post_update_meta_data(project_id, data_name, data_type)
89+
self._put_file_in_project(project_id, data_name + ".le", data_type, body)
90+
self._put_file_in_project(project_id, data_name + ".json", data_type, json.dumps(header))
91+
92+
def _put_file_in_project(self, project_id: str, data_name, data_type: DataTypes, file):
93+
blob_path = data_type.value + "/" + data_name
94+
95+
end_point = f"container/{project_id}/{blob_path}"
96+
response = requests.put(self.host + end_point, data=file, headers=self.header)
97+
98+
if response.status_code >= 400:
99+
raise Exception(f"Request failed: {response.text}")
100+
elif response.status_code >= 200:
101+
print(response.text)
102+
103+
def _post_update_meta_data(self, project_id: str, data_name: str, data_type: DataTypes):
104+
105+
query_param = f"?project_id={project_id}&data_id={data_name}&data_type={data_type.value}"
106+
end_point = "http://localhost:7071/api/update_project_meta" + query_param
107+
108+
response = requests.post(end_point)
109+
110+
if response.status_code >= 400:
111+
raise Exception(f"Request failed: {response.text}")
112+
elif response.status_code >= 200:
113+
print(response.text)
114+
115+
def _post_available_projects(self, available_projects: list):
116+
end_point = "cosmos-le/v1/available_projects/"
117+
response = requests.post(self.host + end_point, json=available_projects,
118+
headers=self.header)
119+
120+
if response.status_code >= 400:
121+
raise Exception(f"Request failed: {response.text}")
122+
elif response.status_code >= 200:
123+
print("Available Projects Posted")
124+
125+
def _post_project_meta(self, meta_data: dict):
126+
project_id = meta_data["id"]
127+
128+
end_point = f"cosmos-le/v1/project_meta/?project_id={project_id}"
129+
response = requests.post(self.host + end_point, json=meta_data, headers=self.header)
130+
131+
if response.status_code >= 400:
132+
raise Exception(f"Request failed: {response.text}")
133+
elif response.status_code >= 200:
134+
print("Project Metadata Posted")

subsurface/structs/base_structures/unstructured_data.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def _validate(self):
207207
raise AttributeError('points_attributes and vertex must have the same length.')
208208

209209
@property
210-
def vertex(self):
210+
def vertex(self) -> np.ndarray:
211211
return self.data['vertex'].values
212212

213213
@property
@@ -252,6 +252,13 @@ def attributes_to_dict(self, orient='list'):
252252
def points_attributes_to_dict(self, orient='list'):
253253
return self.points_attributes.to_dict(orient)
254254

255+
@property
256+
def extent(self):
257+
max = self.vertex.max(axis=0)
258+
min = self.vertex.min(axis=0)
259+
extent = np.stack((min, max), axis = 1).ravel()
260+
return extent
261+
255262
def to_xarray(self):
256263
a = xr.DataArray(self.vertex, dims=['points', 'XYZ'])
257264
b = xr.DataArray(self.cells, dims=['cells', 'node'])
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import pytest
2+
3+
import subsurface
4+
from subsurface.interfaces.liquid_earth.rest_client import LiquidEarthClient, DataTypes
5+
from subsurface.reader.read_netcdf import read_unstruct, read_struct
6+
7+
from subsurface.reader.mesh.surface_reader import dxf_to_mesh
8+
import trimesh
9+
import numpy as np
10+
import pandas
11+
12+
13+
@pytest.mark.skip(reason="This functionality is under development and it is supposed to be trigger"
14+
"only manually.")
15+
class TestLiquidEarthClient:
16+
17+
@pytest.fixture(scope="module")
18+
def liquid_earth_client(self):
19+
c = LiquidEarthClient()
20+
c.login(
21+
"yourUser", # TODO: Add your LiquidEarth Credentials here!
22+
"yourPassword"
23+
)
24+
return c
25+
26+
def test_upload_data_to_new_project(self, liquid_earth_client, data_path):
27+
liquid_earth_client.add_new_project(
28+
"Shafts",
29+
project_id="museum",
30+
extent=[5.87907467e+05, 5.88328512e+05, 6.64175515e+06, 6.64232280e+06, -2.45600098e+02,
31+
7.30000000e+01]
32+
)
33+
34+
unstruc = self.test_dataset2(data_path)
35+
36+
body, header = unstruc.to_binary()
37+
38+
liquid_earth_client.add_data_to_project(
39+
project_id="museum",
40+
data_name="shafts_meshes",
41+
data_type=DataTypes.static_mesh,
42+
header=header,
43+
body=body
44+
)
45+
46+
def test_get_valid_token(self):
47+
client = LiquidEarthClient()
48+
client.login("[email protected]", "demoaccount")
49+
50+
def test_get_available_projects(self, liquid_earth_client):
51+
liquid_earth_client.get_available_projects()
52+
53+
def test_add_new_project(self, liquid_earth_client):
54+
liquid_earth_client.add_new_project(
55+
"subsurface_project",
56+
project_id="52f88baa-c84c-4082-bbba-869ef3819004",
57+
extent=[0, 10, -10, 0, 0, 10]
58+
)
59+
60+
def test_add_data_to_project(self, liquid_earth_client, data_path):
61+
us = read_unstruct(data_path + '/interpolator_meshes.nc')
62+
print(us.extent)
63+
body, header = us.to_binary()
64+
65+
liquid_earth_client.add_data_to_project(
66+
project_id="52f88baa-c84c-4082-bbba-869ef3819004",
67+
data_name="data_from_subsurface",
68+
data_type=DataTypes.static_mesh,
69+
header=header,
70+
body=body
71+
)
72+
73+
def test_update_meta_data(self, liquid_earth_client):
74+
liquid_earth_client._post_update_meta_data(
75+
project_id="52f88baa-c84c-4082-bbba-869ef3819004",
76+
data_name="data_from_subsurface.le",
77+
data_type=DataTypes.static_mesh
78+
)
79+
80+
return
81+
82+
def test_dataset1(self, data_path):
83+
us = read_unstruct(data_path + '/interpolator_meshes.nc')
84+
85+
def test_dataset2(self, data_path):
86+
path = data_path + '/surfaces/shafts.dxf'
87+
88+
vertex, cells, cell_attr_int, cell_attr_map = dxf_to_mesh(path)
89+
90+
tri = trimesh.Trimesh(vertex, faces=cells)
91+
92+
unstruct = subsurface.UnstructuredData.from_array(
93+
np.array(tri.vertices),
94+
np.array(tri.faces),
95+
cells_attr=pandas.DataFrame(cell_attr_int, columns=["Shaft id"]),
96+
xarray_attributes={"bounds": tri.bounds.tolist(),
97+
"cell_attr_map": cell_attr_map
98+
},
99+
)
100+
101+
print(unstruct.extent)
102+
if True:
103+
trisurf = subsurface.TriSurf(unstruct)
104+
s = subsurface.visualization.to_pyvista_mesh(trisurf)
105+
subsurface.visualization.pv_plot([s], image_2d=True)
106+
107+
return unstruct
108+
109+
def test_put_file_in_project(self, liquid_earth_client, data_path):
110+
us = read_unstruct(data_path + '/interpolator_meshes.nc')
111+
print(us.extent)
112+
body, header = us.to_binary()
113+
114+
liquid_earth_client._put_file_in_project(
115+
project_id="52f88baa-c84c-4082-bbba-869ef3819004",
116+
data_name="data_from_subsurface.le",
117+
data_type=DataTypes.static_mesh,
118+
file=body
119+
)
120+
121+
liquid_earth_client._put_file_in_project(
122+
project_id="52f88baa-c84c-4082-bbba-869ef3819004",
123+
data_name="data_from_subsurface.json",
124+
data_type=DataTypes.static_mesh,
125+
file=header
126+
)

0 commit comments

Comments
 (0)