diff --git a/environment_dev.yml b/environment_dev.yml index 7d9785b..4fcafd9 100644 --- a/environment_dev.yml +++ b/environment_dev.yml @@ -7,4 +7,7 @@ dependencies: - pip - pip: - matplotlib + - sphinx_book_theme==0.3.3 + - sphinx_copybutton + - nbsphinx diff --git a/pyborehole/borehole.py b/pyborehole/borehole.py index 828485a..f32f0d7 100644 --- a/pyborehole/borehole.py +++ b/pyborehole/borehole.py @@ -94,6 +94,21 @@ def create_df(self): df : pd.DataFrame DataFrame containing the Borehole Metadata. + Examples + ________ + >>> borehole.create_df() + >>> borehole.df + Value + Name RWE EB1 + Address Am Kraftwerk 17, 52249 Eschweiler, Germany + Location POINT (6.313031 50.835676) + X 6.313031 + Y 50.835676 + Coordinate Reference System EPSG:4326 + Coordinate Reference System PyProj EPSG:4326 + Altitude above sea level 136 + Altitude above KB None + """ # Create dict from attributes df_dict = {'Name': self.name, @@ -133,9 +148,12 @@ def update_df(self, data_dict: dict): df]) def add_deviation(self, - path: str, - delimiter: str, - step: float): + path: Union[str, pd.DataFrame], + delimiter: str = '', + step: float = 1, + md_column: str = 'MD', + dip_column: str = 'DIP', + azimuth_column: str = 'AZI'): """Add deviation to the Borehole Object. Parameters @@ -150,15 +168,19 @@ def add_deviation(self, """ # Create deviation - self.deviation = Deviation(path=path, + self.deviation = Deviation(self, + path=path, delimiter=delimiter, - step=step) + step=step, + md_column=md_column, + dip_column=dip_column, + azimuth_column=azimuth_column) # Updating DataFrame self.update_df(self.deviation.data_dict) def add_well_logs(self, - path): + path: str): """Add Well Logs to the Borehole Object. Parameters @@ -167,13 +189,29 @@ def add_well_logs(self, Path to the well log file """ # Creating well logs - self.logs = Logs(path=path) + self.logs = Logs(self, + path=path) + + def add_well_tops(self, + path: str, + delimiter: str = ','): + """Add Well Tops to the Borehole Object. + + Parameters + __________ + path : str + Path to the well top file + delimiter : str + """ + # Creating well tops + self.well_tops = WellTops(path=path, + delimiter=delimiter) # def read_boreholeml(self): # def write_boreholeml(self): -class Deviation(): +class Deviation(Borehole): """Class to initiate a Deviation object. Parameters @@ -186,10 +224,15 @@ class Deviation(): Step for resampling the deviation data, e.g. ``step=5``. """ + def __init__(self, - path: str, + borehole, + path: Union[str, pd.DataFrame], delimiter: str, - step: float = 5): + step: float = 5, + md_column: str = 'MD', + dip_column: str = 'DIP', + azimuth_column: str = 'AZI'): # Importing wellpathpy try: @@ -198,8 +241,16 @@ def __init__(self, ModuleNotFoundError('wellpathpy package not installed') # Opening deviation file - md, inc, azi = wp.read_csv(fname=path, - delimiter=delimiter) + if isinstance(path, str): + md, inc, azi = wp.read_csv(fname=path, + delimiter=delimiter) + + # Opening Pandas DataFrame + if isinstance(path, pd.DataFrame): + md = path[md_column].values + inc = path[dip_column].values + azi = path[azimuth_column].values + # Creating deviation dev = wp.deviation( @@ -253,10 +304,14 @@ def __init__(self, orient='columns', ) + self.x = borehole.x + self.y = borehole.y + self.z = borehole.altitude_above_sea_level + def add_origin_to_desurveying(self, - x: float =0, - y: float =0, - z: float =0): + x: float = None, + y: float = None, + z: float = None): """Add origin to desurveying. Parameters @@ -269,6 +324,14 @@ def add_origin_to_desurveying(self, Altitude of the origin, e.g. ``z=200``. """ + + if not x: + x = self.x + if not y: + y = self.y + if not z: + z = self.z + # Adding the X coordinate self.desurveyed_df['Northing'] = self.desurveyed_df['Northing_rel'] + y @@ -390,12 +453,28 @@ def lines_from_points(points): tube = spline.tube(radius=radius) # Assigning depth values - tube['TVD'] = tube.points[:,2] + tube['TVD'] = tube.points[:, 2] return tube -class Logs: +class WellTops(Borehole): + """Class to initiate Well Tops. + + Parameters + __________ + path : str + Path to the well tops, e.g. ``path='Well_Tops.csv'``. + + """ + + def __init__(self, + path: str, + delimiter: str = ','): + self.df = pd.read_csv(path, delimiter=delimiter) + + +class Logs(Borehole): """Class to initiate a Well Log Object. Parameters @@ -404,7 +483,10 @@ class Logs: Path to the well logs, e.g. ``path='logs.las'``. """ - def __init__(self, path: str): + + def __init__(self, + borehole, + path: str): # Importing lasio try: @@ -448,31 +530,118 @@ def __init__(self, path: str): 'value', 'descr']) + self.well_tops = borehole.well_tops + def plot_well_logs(self, - tracks: Union[str, list]): + tracks: Union[str, list], + depth_column: str = 'MD', + colors: Union[str, list] = None, + add_well_tops: bool = False, + fill_between: int = None): """Plot well logs Parameters __________ tracks : Union[str, list] - Name/s of the logs to be plotted + Name/s of the logs to be plotted, e.g. ``tracks='SGR'`` or ``tracks=['SGR', 'K']. + depth_column : str + Name of the column holding the depths, e.g. ``depth_column='MD'``. + colors : Union[str, list] + Colors of the logs, e.g. ``colors='black'`` or ``colors=['black', 'blue']. + add_well_tops : bool, default = False + Boolean to add well tops to the plot. """ # Selecting tracks df = self.df[tracks].reset_index() - # Creating plot - fig, ax = plt.subplots(1, len(tracks), figsize=(len(tracks) * 2, 8)) - - # Plotting tracks - for i in range(len(tracks)): - ax[i].plot(df[tracks[i]], df['MD']) - ax[i].grid() - ax[i].invert_yaxis() - buffer = (max(df['MD']) - min(df['MD'])) / 20 - ax[i].set_ylim(max(df['MD']) + buffer, min(df['MD']) - buffer) - - return fig, ax + if isinstance(tracks, str): + # Creating plot + fig, ax = plt.subplots(1, 1, figsize=(1 * 2, 8)) + + ax.plot(df[tracks], df[depth_column], color=colors) + ax.grid() + ax.invert_yaxis() + buffer = (max(df[depth_column]) - min(df[depth_column])) / 20 + ax.set_ylim(max(df[depth_column]) + buffer, min(df[depth_column]) - buffer) + ax.tick_params(top=True, labeltop=True, bottom=False, labelbottom=False) + ax.xaxis.set_label_position('top') + ax.set_xlabel(tracks + ' [%s]' % + self.curves[self.curves['original_mnemonic'] == tracks].reset_index(drop=True)['unit'].iloc[ + 0], color='black') + ax.set_ylabel(depth_column + ' [m]') + + if fill_between: + left_col_value = np.min(df[tracks].dropna().values) + right_col_value = np.max(df[tracks].dropna().values) + span = abs(left_col_value - right_col_value) + cmap = plt.get_cmap('hot_r') + color_index = np.arange(left_col_value, right_col_value, span / 100) + # loop through each value in the color_index + for index in sorted(color_index): + index_value = (index - left_col_value) / span + color = cmap(index_value) # obtain color for color index value + ax.fill_betweenx(df[depth_column], df[tracks], left_col_value, where=df[tracks] >= index, + color=color) + + return fig, ax + + elif isinstance(tracks, list): + + if add_well_tops: + j = 1 + else: + j = 0 + + if not colors: + colors = [None] * len(tracks) + + # Creating plot + fig, ax = plt.subplots(1, + len(tracks) + j, + figsize=(len(tracks) * 1.8, 8), + sharey=True) + + # Helping variable for adding well tops + if add_well_tops: + for index, row in self.well_tops.df.iterrows(): + ax[0].axhline(row[self.well_tops.df.columns[1]], 0, 1, color='black') + ax[0].text(0.05, row[self.well_tops.df.columns[1]] - 1, s=row[self.well_tops.df.columns[0]], + fontsize=6) + ax[0].grid() + ax[0].axes.get_xaxis().set_ticks([]) + + # Plotting tracks + for i in range(len(tracks)): + ax[i + j].plot(df[tracks[i]], df[depth_column], color=colors[i]) + ax[i + j].grid() + ax[i + j].invert_yaxis() + buffer = (max(df[depth_column]) - min(df[depth_column])) / 20 + ax[i + j].set_ylim(max(df[depth_column]) + buffer, min(df[depth_column]) - buffer) + ax[i + j].tick_params(top=True, labeltop=True, bottom=False, labelbottom=False) + ax[i + j].xaxis.set_label_position('top') + ax[i + j].set_xlabel(tracks[i] + ' [%s]' % + self.curves[self.curves['original_mnemonic'] == tracks[i]].reset_index(drop=True)[ + 'unit'].iloc[0], + color='black' if isinstance(colors[i], type(None)) else colors[i]) + ax[0].set_ylabel(depth_column + ' [m]') + + if fill_between is not None: + left_col_value = np.min(df[tracks[fill_between]].dropna().values) + right_col_value = np.max(df[tracks[fill_between]].dropna().values) + span = abs(left_col_value - right_col_value) + cmap = plt.get_cmap('hot_r') + color_index = np.arange(left_col_value, right_col_value, span / 100) + # loop through each value in the color_index + for index in sorted(color_index): + index_value = (index - left_col_value) / span + color = cmap(index_value) # obtain color for color index value + ax[fill_between+j].fill_betweenx(df[depth_column], df[tracks[fill_between]], left_col_value, where=df[tracks[fill_between]] >= index, + color=color) + + plt.tight_layout() + + return fig, ax def plot_well_log_along_path(self, log: str, @@ -688,10 +857,10 @@ def resample_log(line: Union[gpd.GeoDataFrame, LineString], gdf_resampled = pd.concat([pd.DataFrame.from_dict( {'geometry': Point(x[0], y[0]), 'X': [x[0]], 'Y': [y[0]]}, orient='columns'), - gdf_resampled, - pd.DataFrame.from_dict( - {'geometry': Point(x[-1], y[-1]), 'X': [x[-1]], 'Y': [y[-1]]}, - orient='columns') - ], ignore_index=True) + gdf_resampled, + pd.DataFrame.from_dict( + {'geometry': Point(x[-1], y[-1]), 'X': [x[-1]], 'Y': [y[-1]]}, + orient='columns') + ], ignore_index=True) return gdf_resampled diff --git a/pyproject.toml b/pyproject.toml index 3e8251c..3237a55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,6 +75,6 @@ version_scheme = "post-release" # (so there isn't a + in the version) local_scheme = "no-local-version" # This _version_generated.py file is a file that you'll never want to add to version control - so you'll want to add it to your gitignore file. but when you build your package, setuptools_Scm creates it. and it contains version information that you will pull into your package below. -write_to = "pyheatdemand/_version_generated.py" +write_to = "pyborehole/_version_generated.py" write_to_template = '__version__ = "v{version}"' diff --git a/test/test_borehole.py b/test/test_borehole.py index 8279b10..ce5bebc 100644 --- a/test/test_borehole.py +++ b/test/test_borehole.py @@ -28,9 +28,25 @@ def test_borehole_class(): assert isinstance(borehole.crs_pyproj, pyproj.crs.crs.CRS) assert borehole.altitude_above_sea_level == 136 assert isinstance(borehole.altitude_above_sea_level, float) - assert borehole.deviation == None - assert borehole.logs == None + assert borehole.deviation is None + assert borehole.logs is None assert isinstance(borehole.df, pd.DataFrame) + assert borehole.__str__() == 'Weisweiler R1' + + borehole.update_df({'newname': 'Weisweiler R2'}) + assert borehole.df.T['newname'].iloc[0] == 'Weisweiler R2' + + data = {'MD': [0, 50, 100], + 'DIP': [2, 2, 2], + 'AZI': [5, 5, 5]} + + df_dev = pd.DataFrame.from_dict(data) + + borehole.add_deviation(path=df_dev, + step=25, + md_column='MD', + dip_column='DIP', + azimuth_column='AZI') def test_borehole_class_error():