Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TopographicMap module with mesh/property surfaces, fault polygons and official well trajectories #263

Merged
merged 22 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/src/backend/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
RESOURCE_SCOPES_DICT = {
# "sumo": [f"api://{sumo_app_reg['prod']['RESOURCE_ID']}/access_as_user"],
# Note that when switching back to prod, SUMO env in create_sumo_client_instance() must also be changed
"sumo": [f"api://{sumo_app_reg['dev']['RESOURCE_ID']}/access_as_user"],
"sumo": [f"api://{sumo_app_reg['prod']['RESOURCE_ID']}/access_as_user"],
"smda": [SMDA_RESOURCE_SCOPE],
}

Expand Down
2 changes: 2 additions & 0 deletions backend/src/backend/primary/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .routers.grid.router import router as grid_router
from .routers.pvt.router import router as pvt_router
from .routers.well.router import router as well_router
from .routers.surface_polygons.router import router as surface_polygons_router

logging.basicConfig(
level=logging.WARNING,
Expand Down Expand Up @@ -52,6 +53,7 @@ def custom_generate_unique_id(route: APIRoute) -> str:
app.include_router(grid_router, prefix="/grid", tags=["grid"])
app.include_router(pvt_router, prefix="/pvt", tags=["pvt"])
app.include_router(well_router, prefix="/well", tags=["well"])
app.include_router(surface_polygons_router, prefix="/surface_polygons", tags=["surface_polygons"])

authHelper = AuthHelper()
app.include_router(authHelper.router)
Expand Down
Empty file.
19 changes: 18 additions & 1 deletion backend/src/backend/primary/routers/surface/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
from . import schemas


def resample_property_surface_to_mesh_surface(
mesh_surface: xtgeo.RegularSurface, property_surface: xtgeo.RegularSurface
) -> xtgeo.RegularSurface:
"""
Regrid property surface to mesh surface if topology is different
"""
if mesh_surface.compare_topology(property_surface):
return property_surface

mesh_surface.resample(property_surface)
return mesh_surface


def to_api_surface_data(xtgeo_surf: xtgeo.RegularSurface) -> schemas.SurfaceData:
"""
Create API SurfaceData from xtgeo regular surface
Expand All @@ -18,8 +31,12 @@ def to_api_surface_data(xtgeo_surf: xtgeo.RegularSurface) -> schemas.SurfaceData
y_count=xtgeo_surf.nrow,
x_inc=xtgeo_surf.xinc,
y_inc=xtgeo_surf.yinc,
x_min=xtgeo_surf.xmin,
x_max=xtgeo_surf.xmax,
y_min=xtgeo_surf.ymin,
y_max=xtgeo_surf.ymax,
val_min=xtgeo_surf.values.min(),
val_max=xtgeo_surf.values.max(),
rot_deg=xtgeo_surf.rotation,
mesh_data=orjson.dumps(float32values),
mesh_data=orjson.dumps(float32values), # pylint: disable=maybe-no-member,
)
77 changes: 76 additions & 1 deletion backend/src/backend/primary/routers/surface/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from src.services.utils.authenticated_user import AuthenticatedUser
from src.services.utils.perf_timer import PerfTimer
from src.backend.auth.auth_helper import AuthHelper
from src.services.sumo_access.generic_types import SumoContent

from . import converters
from . import schemas

Expand Down Expand Up @@ -42,13 +44,14 @@ def get_static_surface_directory(
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
ensemble_name: str = Query(description="Ensemble name"),
sumo_content_filter: List[SumoContent] = Query(default=None, description="Optional filter by Sumo content type"),
) -> schemas.StaticSurfaceDirectory:
"""
Get a directory of surface names and attributes for static surfaces.
These are the non-observed surfaces that do NOT have time stamps
"""
access = SurfaceAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
surf_dir = access.get_static_surf_dir()
surf_dir = access.get_static_surf_dir(content_filter=sumo_content_filter)

ret_dir = schemas.StaticSurfaceDirectory(
names=surf_dir.names,
Expand Down Expand Up @@ -83,6 +86,78 @@ def get_static_surface_data(
return surf_data_response


@router.get("/property_surface_resampled_to_static_surface/")
def get_property_surface_resampled_to_static_surface(
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
ensemble_name: str = Query(description="Ensemble name"),
realization_num_mesh: int = Query(description="Realization number"),
name_mesh: str = Query(description="Surface name"),
attribute_mesh: str = Query(description="Surface attribute"),
realization_num_property: int = Query(description="Realization number"),
name_property: str = Query(description="Surface name"),
attribute_property: str = Query(description="Surface attribute"),
) -> schemas.SurfaceData:
timer = PerfTimer()

access = SurfaceAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
xtgeo_surf_mesh = access.get_static_surf(real_num=realization_num_mesh, name=name_mesh, attribute=attribute_mesh)
xtgeo_surf_property = access.get_static_surf(
real_num=realization_num_property, name=name_property, attribute=attribute_property
)

if not xtgeo_surf_mesh or not xtgeo_surf_property:
raise HTTPException(status_code=404, detail="Surface not found")

resampled_surface = converters.resample_property_surface_to_mesh_surface(xtgeo_surf_mesh, xtgeo_surf_property)

surf_data_response = converters.to_api_surface_data(resampled_surface)

LOGGER.debug(f"Loaded property surface and created image, total time: {timer.elapsed_ms()}ms")

return surf_data_response


@router.get("/property_surface_resampled_to_statistical_static_surface/")
def get_property_surface_resampled_to_statistical_static_surface(
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
ensemble_name: str = Query(description="Ensemble name"),
statistic_function: schemas.SurfaceStatisticFunction = Query(description="Statistics to calculate"),
name_mesh: str = Query(description="Surface name"),
attribute_mesh: str = Query(description="Surface attribute"),
# statistic_function_property: schemas.SurfaceStatisticFunction = Query(description="Statistics to calculate"),
name_property: str = Query(description="Surface name"),
attribute_property: str = Query(description="Surface attribute"),
) -> schemas.SurfaceData:
timer = PerfTimer()

access = SurfaceAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
service_stat_func_to_compute = StatisticFunction.from_string_value(statistic_function)
if service_stat_func_to_compute is not None:
xtgeo_surf_mesh = access.get_statistical_static_surf(
statistic_function=service_stat_func_to_compute,
name=name_mesh,
attribute=attribute_mesh,
)
xtgeo_surf_property = access.get_statistical_static_surf(
statistic_function=service_stat_func_to_compute,
name=name_property,
attribute=attribute_property,
)

if not xtgeo_surf_mesh or not xtgeo_surf_property:
raise HTTPException(status_code=404, detail="Surface not found")

resampled_surface = converters.resample_property_surface_to_mesh_surface(xtgeo_surf_mesh, xtgeo_surf_property)

surf_data_response = converters.to_api_surface_data(resampled_surface)

LOGGER.debug(f"Loaded property surface and created image, total time: {timer.elapsed_ms()}ms")

return surf_data_response


@router.get("/dynamic_surface_data/")
def get_dynamic_surface_data(
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
Expand Down
4 changes: 4 additions & 0 deletions backend/src/backend/primary/routers/surface/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class SurfaceData(BaseModel):
y_count: int
x_inc: float
y_inc: float
x_min: float
x_max: float
y_min: float
y_max: float
val_min: float
val_max: float
rot_deg: float
Expand Down
Empty file.
21 changes: 21 additions & 0 deletions backend/src/backend/primary/routers/surface_polygons/converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import List
import xtgeo

from . import schemas


def to_api_polygons_data(xtgeo_poly: xtgeo.Polygons) -> List[schemas.PolygonData]:
"""
Create API SurfaceData from xtgeo regular surface
"""
polydata: List[schemas.PolygonData] = []
for poly_id, polygon in xtgeo_poly.dataframe.groupby("POLY_ID"):
polydata.append(
schemas.PolygonData(
x_arr=list(polygon.X_UTME),
y_arr=list(polygon.Y_UTMN),
z_arr=list(polygon.Z_TVDSS),
poly_id=poly_id,
)
)
return polydata
59 changes: 59 additions & 0 deletions backend/src/backend/primary/routers/surface_polygons/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import logging
from typing import List

from fastapi import APIRouter, Depends, HTTPException, Query

from src.services.sumo_access.surface_polygon_access import SurfacePolygonsAccess
from src.services.utils.authenticated_user import AuthenticatedUser
from src.services.utils.perf_timer import PerfTimer
from src.backend.auth.auth_helper import AuthHelper


from . import schemas
from . import converters

LOGGER = logging.getLogger(__name__)

router = APIRouter()


@router.get("/surface_polygons_directory/")
def get_surface_polygons_directory(
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
ensemble_name: str = Query(description="Ensemble name"),
) -> schemas.SurfacePolygonDirectory:
"""
Get a directory of surface polygon names and attributes
"""
access = SurfacePolygonsAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
polygons_dir = access.get_surface_polygons_dir()

return schemas.SurfacePolygonDirectory(
names=polygons_dir.names,
attributes=polygons_dir.attributes,
valid_attributes_for_name=polygons_dir.valid_attributes_for_name,
)


@router.get("/surface_polygons_data/")
def get_surface_polygons_data(
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
ensemble_name: str = Query(description="Ensemble name"),
realization_num: int = Query(description="Realization number"),
name: str = Query(description="Surface name"),
attribute: str = Query(description="Surface attribute"),
) -> List[schemas.PolygonData]:
timer = PerfTimer()

access = SurfacePolygonsAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
xtgeo_poly = access.get_surface_polygons(real_num=realization_num, name=name, attribute=attribute)

if not xtgeo_poly:
raise HTTPException(status_code=404, detail="Surface not found")

poly_data_response = converters.to_api_polygons_data(xtgeo_poly)

LOGGER.debug(f"Loaded static surface and created image, total time: {timer.elapsed_ms()}ms")
return poly_data_response
16 changes: 16 additions & 0 deletions backend/src/backend/primary/routers/surface_polygons/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import List

from pydantic import BaseModel


class SurfacePolygonDirectory(BaseModel):
names: List[str]
attributes: List[str]
valid_attributes_for_name: List[List[int]]


class PolygonData(BaseModel):
x_arr: List[float]
y_arr: List[float]
z_arr: List[float]
poly_id: int | str
25 changes: 14 additions & 11 deletions backend/src/backend/primary/routers/well/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ def get_well_headers(
def get_field_well_trajectories(
# fmt:off
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
# Should be field identifier
case_uuid: str = Query(description="Sumo case uuid"), # Should be field identifier?
unique_wellbore_identifiers:List[str] = Query(None, description="Optional subset of well names")
# fmt:on
) -> List[WellBoreTrajectory]:
"""Get well trajectories for field"""
Expand All @@ -60,22 +60,25 @@ def get_field_well_trajectories(
else:
well_access = WellAccess(authenticated_user.get_smda_access_token())

return well_access.get_field_wellbore_trajectories(field_identifier=field_identifier)
return well_access.get_field_wellbore_trajectories(
field_identifier=field_identifier, unique_wellbore_identifiers=unique_wellbore_identifiers
)


@router.get("/well_trajectory/")
def get_well_trajectory(
@router.get("/well_trajectories/")
def get_well_trajectories(
# fmt:off
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
wellbore_uuid: str = Query(description="Wellbore uuid"),
wellbore_uuids: List[str] = Query(description="Wellbore uuids"),
# fmt:on
) -> WellBoreTrajectory:
"""Get well trajectory"""
) -> List[WellBoreTrajectory]:
"""Get well trajectories"""
well_access: Union[WellAccess, mocked_drogon_smda_access.WellAccess]
if wellbore_uuid in ["drogon_horizontal", "drogon_vertical"]:
# Handle DROGON

# Handle DROGON
if all(x in ["drogon_horizontal", "drogon_vertical"] for x in wellbore_uuids):
well_access = mocked_drogon_smda_access.WellAccess(authenticated_user.get_smda_access_token())
else:
well_access = WellAccess(authenticated_user.get_smda_access_token())

return well_access.get_wellbore_trajectory(wellbore_uuid=wellbore_uuid)
return well_access.get_wellbore_trajectories(wellbore_uuids=wellbore_uuids)
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,32 @@ def get_field_wellbore_trajectories(
),
]

def get_wellbore_trajectory(self, wellbore_uuid: str) -> WellBoreTrajectory:
def get_wellbore_trajectories(self, wellbore_uuids: List[str]) -> List[WellBoreTrajectory]:
"""Get Drogon trajectory"""
if wellbore_uuid == "drogon_horizontal":
return WellBoreTrajectory(
wellbore_uuid="drogon_horizontal",
unique_wellbore_identifier="55/33-A-4",
tvd_msl_arr=[-49.0, 1293.4185, 1536.9384, 1616.4998, 1630.5153, 1656.9874],
md_arr=[0.0, 1477.0, 1761.5, 1899.2601, 2363.9988, 3578.5],
easting_arr=[463256.911, 463564.402, 463637.925, 463690.658, 463910.452, 464465.876],
northing_arr=[5930542.294, 5931057.803, 5931184.235, 5931278.837, 5931688.122, 5932767.761],
trajs: List[WellBoreTrajectory] = []
if "drogon_horizontal" in wellbore_uuids:
trajs.append(
WellBoreTrajectory(
wellbore_uuid="drogon_horizontal",
unique_wellbore_identifier="55/33-A-4",
tvd_msl_arr=[-49.0, 1293.4185, 1536.9384, 1616.4998, 1630.5153, 1656.9874],
md_arr=[0.0, 1477.0, 1761.5, 1899.2601, 2363.9988, 3578.5],
easting_arr=[463256.911, 463564.402, 463637.925, 463690.658, 463910.452, 464465.876],
northing_arr=[5930542.294, 5931057.803, 5931184.235, 5931278.837, 5931688.122, 5932767.761],
)
)
if "drogon_vertical" in wellbore_uuids:
trajs.append(
WellBoreTrajectory(
wellbore_uuid="drogon_vertical",
unique_wellbore_identifier="55/33-1",
tvd_msl_arr=[-25.0, 1774.5],
md_arr=[0.0, 1799.5],
easting_arr=[462480.0, 462480.0],
northing_arr=[5934232.0, 5934232.0],
)
)
return WellBoreTrajectory(
wellbore_uuid="drogon_vertical",
unique_wellbore_identifier="55/33-1",
tvd_msl_arr=[-25.0, 1774.5],
md_arr=[0.0, 1799.5],
easting_arr=[462480.0, 462480.0],
northing_arr=[5934232.0, 5934232.0],
)
return trajs

# type: ignore
# pylint: disable=unused-argument
Expand Down
4 changes: 2 additions & 2 deletions backend/src/services/smda_access/queries/_get_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ def get(access_token: str, endpoint: str, params: dict) -> List[dict]:
results = response.json()["data"]["results"]
next_request = response.json()["data"]["next"]
while next_request is not None:
params["next"] = next_request
params["_next"] = next_request
response = requests.get(urlstring, params=params, headers=headers, timeout=60)
result = response.json()["data"]["results"]
if result:
results.append(response.json()["data"]["results"])
results.extend(response.json()["data"]["results"])
next_request = response.json()["data"]["next"]
elif response.status_code == 404:
print(f"{str(response.status_code) } {endpoint} either does not exists or can not be found")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def get_field_wellbore_trajectories(
"field_identifier": field_identifier,
}
if unique_wellbore_identifiers:
params["unique_wellbore_identifier"] = ", ".join(unique_wellbore_identifiers)
params["unique_wellbore_identifiers"] = ", ".join(unique_wellbore_identifiers)
timer = PerfTimer()
result = get(access_token=access_token, endpoint=endpoint, params=params)
print(f"TIME SMDA fetch wellbore trajectories took {timer.lap_s():.2f} seconds")
Expand Down
Loading