#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''desurvey.py: Contains utilities for desurveying drill hole data'''
__author__ = 'pygeostat development team'
__date__ = '2014-04-03'
__version__ = '3.000'
from math import sin, cos, radians, ceil
import numpy as np
import pandas as pd
from ..data import DataFile
import warnings
from collections import OrderedDict
import xml.etree.cElementTree as ET
import copy
from .. pygeostat_parameters import Parameters
[docs]
class Drillhole(object):
'''This class contains specific drill hole data and metadata.
Drill hole classes may be created or generated using pygeostat functions.
This is primarily used for desurveying drill hole data.
Parameters:
holeid (str): Hole Id
collarx (numeric): x coordinate
collary (numeric): y coordinate
collarz (numeric): z coordinate
.. codeauthor: pygeostat development team 2014-04-03'''
def __init__(self, holeid=None, collarx=None, collary=None, collarz=None):
self.holeid = holeid
self.collarx = float(collarx)
self.collary = float(collary)
self.collarz = float(collarz)
self.data = pd.DataFrame()
def __str__(self):
return('Drillhole {} collared at ({},{},{})'.format(self.holeid, self.collarx,
self.collary, self.collarz))
[docs]
def delx(self, dist, inclination, bearing):
'''Calculate change in X
Parameters:
self
dist (float): distance along direction
inclination (float)
bearing (float)
Returns:
Value (float): A float value of the change in distance
.. codeauthor: pygeostat development team 2014-04-03'''
val = dist * cos(radians(inclination)) * sin(radians(bearing))
return val
[docs]
def dely(self, dist, inclination, bearing):
'''Calculate change in y
Parameters:
self:
dist (float): distance along direction
inclination (float)
bearing (float)
Returns:
Value (float): A float value of the change in distance
.. codeauthor: pygeostat development team 2014-04-03'''
val = dist * cos(radians(inclination)) * cos(radians(bearing))
return val
[docs]
def delz(self, dist, inclination, bearing):
'''Calculate change in z
Parameters:
self
dist (float): distance along direction
inclination (float)
bearing (float): Not currently used in the calculation
Returns:
Value (float): A float value of the change in distance
.. codeauthor: pygeostat development team 2014-04-03'''
val = dist * sin(radians(inclination))
return val
[docs]
def getxyz(self, along, null):
'''Return X, Y, Z values given the length along the drill hole
Parameters:
self:
along (numeric): Distance along the drill hole
Returns:
x, y, z (float)
.. codeauthor: pygeostat development team 2014-04-03'''
x, y, z = (self.collarx, self.collary, self.collarz)
# Check some base cases
if (along < 0.) or (along > float(self.data['Along'][-1:])):
x, y, z = (null, null, null)
print('ERROR: This along is invalid ', along, 'for', self.holeid)
elif along == 0.:
pass
else:
loc = 0.
idx = 0
while loc < along:
try:
alongcheck = self.data['Along'][idx + 1]
except KeyError:
alongcheck = 1e21
print('WARNING: Hole {} only has 1 survey station, duplicating it'.format(self.holeid))
idx = idx - 1
if along >= alongcheck:
dist = self.data['Along'][idx + 1] - loc
loc = self.data['Along'][idx + 1]
idx += 1
x += self.delx(dist, self.data['Inclination'][idx],
self.data['Azimuth'][idx])
y += self.dely(dist, self.data['Inclination'][idx],
self.data['Azimuth'][idx])
z += self.delz(dist, self.data['Inclination'][idx],
self.data['Azimuth'][idx])
else:
dist = along - loc
loc = along
idx += 1
x += self.delx(dist, self.data['Inclination'][idx],
self.data['Azimuth'][idx])
y += self.dely(dist, self.data['Inclination'][idx],
self.data['Azimuth'][idx])
z += self.delz(dist, self.data['Inclination'][idx],
self.data['Azimuth'][idx])
return(x, y, z)
[docs]
def set_desurvey(collarfl, surveyfl, along_name, azimuth_name, inclination_name):
'''Sets up for desurveying returning a dictionary of DrillHole objects
Parameters:
collarfl (DataFile): pygeostat DataFile with dh, x, y, z all set
surveyfl (DataFile): pygeostat DataFile with dh set
along_name (str): variable name of "along" in survey file
azimuth_name (str): variable name of "azimuth/bearing" in survey file
inclination_name (str): variable name of "inclination" in survey file
Returns:
drillholes (dict): Returns a dictionary of DrillHole objects
.. codeauthor:: pygeostat development team - 2014-04-03'''
print(("\nWARNING: drillhole routines are untested with recent Pygeostat changes!\n"
" Proceed with caution!"))
# Check for missing components
for item in [collarfl.dh, collarfl.x, collarfl.y, collarfl.z,
surveyfl.dh, surveyfl.data[along_name],
surveyfl.data[azimuth_name], surveyfl.data[inclination_name]]:
pass
# Create drill hole objects with collar information
drillholes = {}
for rowidx, row in collarfl.data.iterrows():
drillholes[row[collarfl.dh]] = Drillhole(holeid=row[collarfl.dh], collarx=row[collarfl.x],
collary=row[collarfl.y], collarz=row[collarfl.z])
# Get along, azimuth and inclination data for this drill hole
for dhid in drillholes:
# Find rows with this drill hole ID
rows = surveyfl.data[surveyfl.data[surveyfl.dh] == dhid].index.values
# Check that rows were actually found
if len(rows) == 0:
raise Exception('No survey information found for' + drillholes[dhid])
# Attach the data to the Drillhole
drillholes[dhid].data = pd.DataFrame({'Along': surveyfl.data[along_name].ix[rows],
'Azimuth': surveyfl.data[azimuth_name].ix[rows],
'Inclination': surveyfl.data[inclination_name].ix[rows]})
# Sort by along to ensure ordered values and reset the index
drillholes[dhid].data = drillholes[dhid].data.sort_values(by='Along')\
.reset_index()[['Along', 'Azimuth', 'Inclination']]
return drillholes
[docs]
def get_desurvey(datafl, drillholes, inplace=True, x='X', y='Y', z='Z', null=None):
'''Gets the desurvey of a DataFile given froms and tos.
Parameters:
datafl (DataFile): pygeostat DataFile to be desurveyed with one of the options:
1) just ifrom or just ito set to desurvey at exactly that distance
2) both ifrom and ito set to desurvey halfway in between (ie: midpoint)
drillholes (dict): dictionary of Drillhole objects, obtained from set_desurvey
Keyword Args:
inplace (bool): modifies the datafl.data to have X, Y and Z values
otherwise returns a pandas dataframe with X, Y and Z
.. codeauthor: pygeostat development team 2014-04-03'''
# Create a temporary data file - this allows variable creation
# without leaving behind a mess or overwriting anything accidentally!
# There are probably cleaner ways to do this, but desurveying is normally a 1-off.
print(("\nWARNING: drillhole routines are untested with recent Pygeostat changes!\n"
" Proceed with caution!"))
if null is None:
null = Parameters['data.null']
tempfl = pd.DataFrame(datafl.data[datafl.dh])
# Get the midpoint, or 'along' value to use
if datafl.ifrom is not None and datafl.ito is not None:
tempfl['mid'] = (datafl.data[datafl.ito] + datafl.data[datafl.ifrom]) * 0.5
elif datafl.ifrom is not None and datafl.ito is None:
tempfl['mid'] = datafl.data[datafl.ifrom]
elif datafl.ifrom is None and datafl.ito is not None:
tempfl['mid'] = datafl.data[datafl.ito]
else:
raise Exception('Both ifrom and ito are missing from datafl!')
# Define a local desurvey function
def desurvey(row):
'Define a local desurvey function'
# Get the corresponding drill hole
drillhole = drillholes[row[datafl.dh]]
# Get the X,Y,Z coordinates
xloc, yloc, zloc = drillhole.getxyz(row['mid'], null)
return pd.Series({x: xloc, y: yloc, z: zloc})
# Desurvey
xyzpoints = tempfl.apply(desurvey, axis=1)
if not inplace:
return xyzpoints
else:
# Assign data
datafl.data[x] = xyzpoints[x]
datafl.data[y] = xyzpoints[y]
datafl.data[z] = xyzpoints[z]
# Set datafl.x, datafl.y, datafl.z
if datafl.x is None:
datafl.x = x
if datafl.y is None:
datafl.y = y
if datafl.z is None:
datafl.z = z
return None
[docs]
def set_comps(datafl, complength):
'''Returns pandas dataframe with drillhole, compfrom, compto
Parameters:
datafl (DataFile): pygeostat DataFile with dh, ifrom and ito set
complength (numeric): length of composites
Returns:
comps (DataFrame): pandas DataFrame with dh, ifrom and ito set for each composite
.. codeauthor: pygeostat development team 2014-04-03'''
print(("\nWARNING: drillhole routines are untested with recent Pygeostat changes!\n"
" Proceed with caution!"))
# Get list of drill holes
dhids = datafl.unique_cats(datafl.data[datafl.dh])
# Determine start (smallest 'from' value) and end (largest 'to' value) of each dh
starts = {}
ends = {}
nsamples = {}
totalsamples = 0
for dhid in dhids:
starts[dhid] = min(datafl.data[datafl.data[datafl.dh] == dhid][datafl.ifrom])
ends[dhid] = max(datafl.data[datafl.data[datafl.dh] == dhid][datafl.ito])
# Determine number of composites in each
nsamples[dhid] = ceil((ends[dhid] - starts[dhid]) / complength)
ends[dhid] = nsamples[dhid] * complength + starts[dhid]
totalsamples += nsamples[dhid]
# Initialize pandas dataframe
datadict = OrderedDict()
datadict[datafl.dh] = [dhid for dhid in range(int(totalsamples))]
datadict[datafl.ifrom] = np.ones(int(totalsamples))
datadict[datafl.ito] = np.ones(int(totalsamples))
comps = pd.DataFrame(datadict)
# Composites
startidx = 0
for dhid in dhids:
# Pandas slices using INCLUSIVE INDEXING therefore subtract 1 and add 1 later
endidx = startidx + nsamples[dhid] - 1
# Set drill hole number, from and to
comps.loc[startidx:endidx, datafl.dh] = dhid
comps.loc[startidx:endidx, datafl.ifrom] = np.linspace(starts[dhid],
ends[dhid] - complength,
num=nsamples[dhid], endpoint=True)
comps.loc[startidx:endidx, datafl.ito] = np.linspace(starts[dhid] + complength, ends[dhid],
num=nsamples[dhid], endpoint=True)
startidx = endidx + 1
return comps
[docs]
def get_comps(comps, datafl, vartypes='continuous', null=None, nprocess=None):
'''Returns a pandas DataFrame with upscaled composites parallelizing across
drillholes.
Parameters:
datafl (DataFile): pygeostat DataFile with dh, ifrom, ito and at least 1 variable to upscale
comps (DataFrame): pandas DataFrame with datafl.dh, datafl.ifrom, datafl.ito
corresponding to the composite locations
Keyword Args:
vartypes (str OR dict): 'continuous', 'categorical' or a dictionary of variables like:
{'Cu':'continuous','Facies','categorial'}
null: the null value (values less than or equal to this will not be used)
Returns:
upscaled (DataFrame): Pandas DataFrame with values of upscaled variable
.. codeauthor: pygeostat development team 2014-04-03'''
import multiprocessing as mp
print(("\nWARNING: drillhole routines are untested with recent Pygeostat changes!\n"
" Proceed with caution!"))
if null is None:
null = Parameters['data.null']
# Get a list of drill holes for composites
dhids = sorted(list(set(comps[datafl.dh])))
# Determine the number of composites
ncomps = len(comps[datafl.dh])
# Create a dictionary of variable upscaling methods if not supplied
if isinstance(vartypes, str):
vartypedict = {}
for var in datafl.data.columns:
if var not in [datafl.dh, datafl.ifrom, datafl.ito]:
vartypedict[var] = vartypes.lower()
else:
vartypedict = vartypes
# Determine number of variables
nvar = len(vartypedict)
# Initialize a pandas DataFrame for upscaled values
upscaled = pd.DataFrame(np.ones([ncomps, nvar]) * null, columns=list(vartypedict))
upscaled[datafl.dh] = comps[datafl.dh]
upscaled[datafl.ifrom] = comps[datafl.ifrom]
upscaled[datafl.ito] = comps[datafl.ito]
# Group data by drill hole ID
grouped_comps = comps.groupby([datafl.dh], squeeze=True, as_index=False)
grouped_data = datafl.data.groupby([datafl.dh], squeeze=True, as_index=False)
# Upscale all drill holes in parallel asynchronously
if nprocess is None:
nprocess = Parameters['config.nprocess']
# Create a pool for processing, calling with None to use all CPUs
for var, vartype in vartypedict.items():
# print('Assembling pool for {}...'.format(var))
pool = mp.Pool(processes=nprocess)
results = {}
number_of_valid_dhs = 0
for dhid, compdf in grouped_comps:
# Get the data which corresponds to this composite
try:
datadf = grouped_data.get_group(dhid)
number_of_valid_dhs += 1
except KeyError:
# No data for this composite drill hole so skip it
continue
# Add on the GSLIB call to execute asynchronously
results[dhid] = pool.apply_async(upscale_worker, (compdf, datadf, var, vartype,
datafl.ifrom, datafl.ito, null))
# Close the list of processes for the pool
pool.close()
# "Join" the list of processes to execute and wait for completion
pool.join()
# Tabulate the results that are not null (ie: a float)
try:
all_results = (pd.concat([group.get() for name, group in results.items()
if type(group.get()) is not float]))
except ValueError:
# No data were found
warnings.warn('No valid samples found for variable {}!!!'.format(var))
return(upscaled)
# Join this to our database
upscaled[var] = upscaled[var].replace({null: np.nan})
upscaled[var].fillna(all_results, inplace=True)
upscaled[var].fillna(value=null, inplace=True)
return(upscaled)
def upscale_worker(compdata, dhdata, var, vartype, ifrom, ito, null=None):
'''Returns the upscaled value contained in [compfrom,compto]
Parameters:
compdata (DataFrame): pandas DataFrame with columns for ifrom, ito for the DH of interest
dhdata (DataFrame): pandas DataFrame with columns for ifrom, ito and var for the DH
of interest
var (str): variable name
vartype (str): 'continuous' or 'categorical'
Keyword Args:
null: value to return if no data in interval
Returns:
compdata[var]: pandas series of upscaled variable using either:
continuous - length weighted linear average or
categorical - most frequent category
.. codeauthor: pygeostat development team 2014-04-03'''
if null is None:
null = Parameters['data.null']
# Initialize the upscaled variable
compdata[var] = np.ones(len(compdata[ifrom])) * null
# Iterate over composite rows
for compidx, comprow in compdata.iterrows():
compfrom = comprow[ifrom]
compto = comprow[ito]
# Define local function to get length in interval
def length_in_interval(datarow):
'finds the length in the interval along the drill hole - tda'
# Is the variable value valid?
if datarow[var] <= null:
return 0.0
# Case 1 - entire sample interval in composite
if (datarow[ifrom] >= compfrom) and (datarow[ito] <= compto):
return datarow[ito] - datarow[ifrom]
# Case 2 - partially bottom of interval
elif ((datarow[ifrom] >= compfrom) and (datarow[ifrom] < compto) and
(datarow[ito] > compto)):
return compto - datarow[ifrom]
# Case 3 - partially in top of interval
elif ((datarow[ifrom] < compfrom) and (datarow[ito] > compfrom) and
(datarow[ito] <= compto)):
return datarow[ito] - compfrom
# Case 4 - composite bracketed by sample interval
elif (datarow[ifrom] <= compfrom) and (datarow[ito] >= compto):
return compto - compfrom
# Case 5 - no length in interval
else:
return 0.0
# Get length of each sample in composite
try:
dhdata['Length'] = dhdata.apply(length_in_interval, axis=1)
except ValueError as e: # No valid data in drill hole found - null assignment!
return null
# If no data was found, return null value
if sum(dhdata['Length']) <= 0.0:
upscaled = null
else:
# Upscale the data
# - Continuous
if vartype.startswith('con'):
upscaled = np.dot(dhdata[var], dhdata['Length']) / sum(dhdata['Length'])
# - Categorical
elif vartype.startswith('cat'):
unique_cats = list(set(dhdata[dhdata['Length'] > 0.0][var]))
unique_cat_wts = [sum(dhdata[dhdata[var] == cat]['Length']) for cat in unique_cats]
upscaled = unique_cats[unique_cat_wts.index(max(unique_cat_wts))]
# - Invalid variable type
else:
raise Exception('Invalid vartype', vartype)
# Save this value
compdata.loc[compidx, var] = upscaled
# Return all values for this drill hole
return(compdata[var])
def serial_upscale(compdh, compfrom, compto, datafl, var, vartype, null=None):
'''Returns the upscaled value contained in [compfrom,compto]
Parameters:
compdh: composite drill hole
compfrom: composite 'from'
compto: composite 'to'
datafl (DataFile): pygeostat DataFile with dh, ifrom, ito and var
var (str): variable name
vartype (str): 'continuous' or 'categorical'
Keyword Args:
null: value to return if no data in interval
Returns:
upscaled: value of upscaled variable using either:
continuous - length weighted linear average or
categorical - most frequent category
.. codeauthor: pygeostat development team 2014-04-03'''
if null is None:
null = Parameters['data.null']
# Get the variable, froms and tos which are in that drill hole
dhdata = datafl.data[datafl.data[datafl.dh] == compdh][[datafl.ifrom, datafl.ito, var]]
# Define local function to get length in interval
def length_in_interval(datarow):
'finds the length in the interval along the drill hole - tda'
# Is the variable value valid?
if datarow[var] <= null:
return 0.0
# Case 1 - entire sample interval in composite
if (datarow[datafl.ifrom] >= compfrom) and (datarow[datafl.ito] <= compto):
return datarow[datafl.ito] - datarow[datafl.ifrom]
# Case 2 - partially bottom of interval
elif ((datarow[datafl.ifrom] >= compfrom) and (datarow[datafl.ifrom] < compto) and
(datarow[datafl.ito] > compto)):
return compto - datarow[datafl.ifrom]
# Case 3 - partially in top of interval
elif ((datarow[datafl.ifrom] < compfrom) and (datarow[datafl.ito] > compfrom) and
(datarow[datafl.ito] <= compto)):
return datarow[datafl.ito] - compfrom
# Case 4 - composite bracketed by sample interval
elif (datarow[datafl.ifrom] <= compfrom) and (datarow[datafl.ito] >= compto):
return compto - compfrom
# Case 5 - no length in interval
else:
return 0.0
# Get length of each sample in composite
try:
dhdata['Length'] = dhdata.apply(length_in_interval, axis=1)
except ValueError: # No valid data in drill hole found - null assignment!
return null
# If no data was found, return null value
if sum(dhdata['Length']) <= 0.0:
return null
# Upscale the data
# - Continuous
if vartype.startswith('con'):
upscaled = np.dot(dhdata[var], dhdata['Length']) / sum(dhdata['Length'])
# - Categorical
elif vartype.startswith('cat'):
unique_cats = list(set(dhdata[dhdata['Length'] > 0.0][var]))
unique_cat_wts = [sum(dhdata[dhdata[var] == cat]['Length']) for cat in unique_cats]
upscaled = unique_cats[unique_cat_wts.index(max(unique_cat_wts))]
# - Invalid variable type
else:
raise Exception('Invalid vartype', vartype)
return upscaled
def serial_get_comps(comps, datafl, vartypes='continuous', null=None):
'''Returns a pandas DataFrame with upscaled composites
Parameters:
datafl (DataFile): pygeostat DataFile with dh, ifrom, ito and at least 1 variable to upscale
comps (DataFrame): pandas DataFrame with datafl.dh, datafl.ifrom, datafl.ito
corresponding to the composite locations
Keyword Args:
vartypes (str OR dict): 'continuous', 'categorical' or a dictionary of variables like:
{'Cu':'continuous','Facies','categorial'}
null: the null value (values less than or equal to this will not be used)
Returns:
upscaled (DataFrame): Pandas DataFrame with values of upscaled variable
.. codeauthor: pygeostat development team 2014-04-03'''
if null is None:
null = Parameters['data.null']
# Get a list of drill holes for composites
dhids = sorted(list(set(comps[datafl.dh])))
# Determine the number of composites
ncomps = len(comps[datafl.dh])
# Create a dictionary of variable upscaling methods if not supplied
if isinstance(vartypes, str):
vartypedict = {}
for var in datafl.data.columns:
if var not in [datafl.dh, datafl.ifrom, datafl.ito]:
vartypedict[var] = vartypes.lower()
else:
vartypedict = vartypes
# Determine number of variables
nvar = len(vartypedict)
# Initialize a pandas DataFrame for upscaled values
upscaled = pd.DataFrame(np.ones([ncomps, nvar]) * null, columns=list(vartypedict))
upscaled[datafl.dh] = comps[datafl.dh]
upscaled[datafl.ifrom] = comps[datafl.ifrom]
upscaled[datafl.ito] = comps[datafl.ito]
# Upscale and return
for var, vartype in vartypedict.items():
# Define the variable specific upscaling function to apply
def varupscale(compseries):
'Define the variable specific upscaling function to apply'
return(upscale(compseries[datafl.dh], compseries[datafl.ifrom],
compseries[datafl.ito], datafl, var, vartype, null))
# Apply this to the variable
upscaled[var] = comps.apply(varupscale, axis=1)
return upscaled
[docs]
def fast_comps(comps, datafl, null=None):
'''Returns a pandas DataFrame with upscaled composites
and ASSUMES NO MISSING VALUES
Parameters:
datafl (DataFile): pygeostat DataFile with dh, ifrom, ito and at least 1 variable to upscale
comps (DataFrame): pandas DataFrame with datafl.dh, datafl.ifrom, datafl.ito
corresponding to the composite locations
Keyword Args:
null: the null value (values less than or equal to this will not be used)
Returns:
upscaled (DataFrame): Pandas DataFrame with value of upscaled variable
.. codeauthor: pygeostat development team 2014-04-03'''
if null is None:
null = Parameters['data.null']
# Get a list of drill holes for composites
dhids = sorted(list(set(comps[datafl.dh])))
# Determine the number of composites
ncomps = len(comps[datafl.dh])
# Create a dictionary of variable upscaling methods if not supplied
vartypedict = {}
for var in datafl.data.columns:
if var not in [datafl.dh, datafl.ifrom, datafl.ito]:
vartypedict[var] = 'continuous'
# Determine number of variables
nvar = len(vartypedict)
# Initialize a pandas DataFrame for upscaled values
upscaled = pd.DataFrame(np.ones([ncomps, nvar]) * null, columns=list(vartypedict))
upscaled[datafl.dh] = comps[datafl.dh]
upscaled[datafl.ifrom] = comps[datafl.ifrom]
upscaled[datafl.ito] = comps[datafl.ito]
# Now upscale
for rowidx, row in comps.iterrows():
compfrom = row[datafl.ifrom]
compto = row[datafl.ito]
# Get the variable, froms and tos which are in that drill hole
dhdata = datafl.data[datafl.data[datafl.dh] == row[datafl.dh]]
# Define local function to get length in interval
def length_in_interval(datarow):
'Define local function to get length in interval'
# Is the variable value valid?
if datarow[var] <= null:
return 0.0
# Case 1 - entire sample interval in composite
if (datarow[datafl.ifrom] >= compfrom) and (datarow[datafl.ito] <= compto):
return datarow[datafl.ito] - datarow[datafl.ifrom]
# Case 2 - partially bottom of interval
elif ((datarow[datafl.ifrom] >= compfrom) and (datarow[datafl.ifrom] < compto) and
(datarow[datafl.ito] > compto)):
return compto - datarow[datafl.ifrom]
# Case 3 - partially in top of interval
elif ((datarow[datafl.ifrom] < compfrom) and (datarow[datafl.ito] > compfrom) and
(datarow[datafl.ito] <= compto)):
return datarow[datafl.ito] - compfrom
# Case 4 - composite bracketed by sample interval
elif (datarow[datafl.ifrom] <= compfrom) and (datarow[datafl.ito] >= compto):
return compto - compfrom
# Case 5 - no length in interval
else:
return 0.0
# Get length of each sample in composite
dhdata['Length'] = dhdata.apply(length_in_interval, axis=1)
# If no data was found, return null value
if sum(dhdata['Length']) <= 0.0:
for var, vartype in vartypedict.items():
upscaled.loc[rowidx, var] = null
# Upscale the data
for var, vartype in vartypedict.items():
upscaled.loc[rowidx, var] = np.dot(dhdata[var],
dhdata['Length']) / sum(dhdata['Length'])
return upscaled
[docs]
def write_vtp(datafl, vartypes, drillholes, outflname, complength=5.0, null=None):
'''Generates a VTP file compatible with ParaView.
Parameters:
datafl (DataFile): pygeostat DataFile with dh, ifrom, ito and at least 1 variable to upscale
vartypes (dict): dictionary of variable types like {'Grade':'continuous', 'Category':'categorical'}
drillholes (dict): dictionary of drillhole IDs to pygeostat DrillHole objects
likely obtained by running:
drillholes = gs.set_desurvey(collarfl, surveyfl, 'Depth', 'Azimuth', 'Inclination')
outflname (str): output VTP file to generate
Keyword Args:
null: the null value (values equal to this will not be used)
complength: this is the composite length which everything is regularized to which helps
prevent visual artefacts and long triangles with a tube filter
.. codeauthor: pygeostat development team 2017'''
from ..datautils import fastcomps as gsfastcomps
if null is None:
null = Parameters['data.null']
# "Composite" it down to reduce visual artefacts
tmpfl = copy.deepcopy(datafl)
comps = set_comps(datafl, complength)
tmpfl.data = gsfastcomps.get_comps(comps, datafl, vartypes=vartypes, null=null)
# Slice out any assays exceeding maximum hole depth
tmpdfs = []
for dhid, drillhole in drillholes.iteritems():
tmpdfs.append(tmpfl.data[(tmpfl.data[tmpfl.dh] == dhid) &
(tmpfl.data[tmpfl.ifrom] <= max(drillhole.data['Along'])) &
(tmpfl.data[tmpfl.ito] <= max(drillhole.data['Along']))])
tmpfl.data = pd.concat(tmpdfs, ignore_index=False)
# Desurvey in all permutations...
# Mid desurveying
get_desurvey(tmpfl, drillholes, inplace=True, x='MIDX', y='MIDY', z='MIDZ')
# Top desurveying
tmp_name = tmpfl.ito
tmpfl.ito = None
get_desurvey(tmpfl, drillholes, inplace=True, x='TOPX', y='TOPY', z='TOPZ')
# Bottom desurveying
tmpfl.ito = tmp_name
tmp_name = tmpfl.ifrom
tmpfl.ifrom = None
get_desurvey(tmpfl, drillholes, inplace=True, x='BOTTOMX', y='BOTTOMY', z='BOTTOMZ')
tmpfl.ifrom = tmp_name
# Now generate the VTK file
root = ET.Element('VTKFile', type="PolyData", version="1.0", byte_order="LittleEndian", header_type="UInt64")
pdata = ET.SubElement(root, 'PolyData')
# Each drillhole is a separate "Piece" of "PolyData" in the VTK File
for dhid, drillhole in drillholes.iteritems():
dhdf = tmpfl.data[tmpfl.data[tmpfl.dh] == dhid]
nverts = len(dhdf) + 2 # Add 2 since we have the top and bottom extra vertices
# Header
piece = ET.SubElement(pdata, 'Piece', NumberOfPoints="{}".format(nverts), NumberOfVerts="0",
NumberOfLines="1", NumberOfStrips="0", NumberOfPolys="0")
# Vertex coordinates
points = ET.SubElement(piece, 'Points')
darray = ET.SubElement(points, 'DataArray', type="Float32", Name="Points",
NumberOfComponents="3", format="ascii")
# Top and bottom vertices are added to make the drill hole match the true length
ptarray = ' '.join(map(str, dhdf[['TOPX', 'TOPY', 'TOPZ']].values[0]))
ptarray = ptarray + ' ' + ' '.join([' '.join(map(str, row[['MIDX', 'MIDY', 'MIDZ']].values)) for \
rowidx, row in dhdf.iterrows()])
ptarray = ptarray + ' ' + ' '.join(map(str, dhdf[['BOTTOMX', 'BOTTOMY', 'BOTTOMZ']].values[-1]))
darray.text = ptarray
# Variable values
pointdata = ET.SubElement(piece, 'PointData', Scalars="DATA")
for varname, vartype in vartypes.iteritems():
if vartype.lower().startswith('cat'):
vtktype = 'Int32'
ptarray = str(int(dhdf[varname].values[0])) + ' ' + \
' ' .join(map(str, map(int, dhdf[varname].values))) + ' ' + \
str(int(dhdf[varname].values[-1]))
else:
vtktype = 'Float32'
ptarray = str(dhdf[varname].values[0]) + ' ' + \
' ' .join(map(str, dhdf[varname].values)) + ' ' + \
str(dhdf[varname].values[-1])
darray = ET.SubElement(pointdata, 'DataArray', type=vtktype, Name=varname, format="ascii")
darray.text = ptarray
# Line values
lines = ET.SubElement(piece, 'Lines')
darray = ET.SubElement(lines, 'DataArray', type="Int64", Name="connectivity", format="ascii")
darray.text = ' '.join(map(str, range(0,nverts)))
darray = ET.SubElement(lines, 'DataArray', type="Int64", Name="offsets", format="ascii")
darray.text = '{}'.format(nverts)
tree = ET.ElementTree(root)
tree.write(outflname)