import numpy as np import matplotlib.pyplot as plt from shapely.geometry import Point, LineString import geopandas as gpd import pandas as pd from fiona.crs import from_epsg %matplotlib inline class Survey: """ A seismic survey. """ def __init__(self, params): # Assign the variables from the parameter dict, # using dict.items() for Python 3 compatibility. for k, v in params.items(): setattr(self, k, v) # These are just a convenience; we could use the # tuples directly, or make objects with attrs. self.xmi = self.corner[0] self.ymi = self.corner[1] self.x = self.size[0] self.y = self.size[1] self.SL = self.line_spacing[0] self.RL = self.line_spacing[1] self.si = self.point_spacing[0] self.ri = self.point_spacing[1] self.shiftx = -self.si/2. self.shifty = -self.ri/2. @property def lines(self): """ Returns number of (src, rcvr) lines. """ slines = int(self.x/self.SL) + 1 rlines = int(self.y/self.RL) + 1 return slines, rlines @property def points_per_line(self): """ Returns number of (src, rcvr) points per line. """ spoints = int(self.y/self.si) + 2 rpoints = int(self.x/self.ri) + 2 return spoints, rpoints @property def src(self): s = [Point(self.xmi+line*self.SL, self.ymi+s*self.si) for line in range(self.lines[0]) for s in range(self.points_per_line[0]) ] S = gpd.GeoSeries(s) S.crs = from_epsg(26911) return S @property def rcvr(self): r = [Point(self.xmi + r*self.ri + self.shiftx, self.ymi + line*self.RL - self.shifty) for line in range(self.lines[1]) for r in range(self.points_per_line[1]) ] R = gpd.GeoSeries(r) R.crs = from_epsg(self.epsg) return R @property def layout(self): """ Provide a GeoDataFrame of all points, labelled as columns and in hierarchical index. """ # Feels like there might be a better way to do this... sgdf = gpd.GeoDataFrame({'geometry': self.src, 'station': 'src'}) rgdf = gpd.GeoDataFrame({'geometry': self.rcvr, 'station': 'rcvr'}) # Concatenate with a hierarchical index layout = pd.concat([sgdf,rgdf], keys=['sources','receivers']) layout.crs = from_epsg(self.epsg) return layout params = {'corner': (5750000,4710000), 'size': (3000,1800), 'line_spacing': (600,600), 'point_spacing': (100,100), 'epsg': 26911 # http://spatialreference.org/ref/epsg/26911/ } survey = Survey(params) s = survey.src r = survey.rcvr r[:10] layout = survey.layout layout[:10] layout.ix['sources'][-5:] layout.crs ax = layout.plot() # gdf.to_file('src_and_rcvr.shp') midpoint_list = [LineString([r, s]).interpolate(0.5, normalized=True) for r in layout.ix['receivers'].geometry for s in layout.ix['sources'].geometry ] offsets = [r.distance(s) for r in layout.ix['receivers'].geometry for s in layout.ix['sources'].geometry ] azimuths = [(180.0/np.pi) * np.arctan((r.x - s.x)/(r.y - s.y)) for r in layout.ix['receivers'].geometry for s in layout.ix['sources'].geometry ] offsetx = np.array(offsets)*np.cos(np.array(azimuths)*np.pi/180.) offsety = np.array(offsets)*np.sin(np.array(azimuths)*np.pi/180.) midpoints = gpd.GeoDataFrame({ 'geometry' : midpoint_list, 'offset' : offsets, 'azimuth': azimuths, 'offsetx' : offsetx, 'offsety' : offsety }) midpoints[:5] ax = midpoints.plot() #midpt.to_file('CMPs.shp') midpoints[:5].offsetx # Easy! midpoints.ix[3].geometry.x # Less easy :( x = [m.geometry.x for i, m in midpoints.iterrows()] y = [m.geometry.y for i, m in midpoints.iterrows()] fig = plt.figure(figsize=(12,8)) plt.quiver(x, y, midpoints.offsetx, midpoints.offsety, units='xy', width=0.5, scale=1/0.025, pivot='mid', headlength=0) plt.axis('equal') plt.show() # Factor to shift the bins relative to source and receiver points jig = survey.si / 4. bin_centres = gpd.GeoSeries([Point(survey.xmi + 0.5*r*survey.ri - jig, survey.ymi + 0.5*s*survey.si + jig) for r in range(2*(survey.points_per_line[1]-1)) for s in range(2*(survey.points_per_line[0]-1)) ]) # Buffers are diamond shaped so we have to scale and rotate them. scale_factor = np.sin(np.pi/4.)/2. bin_polys = bin_centres.buffer(scale_factor*survey.ri, 1).rotate(-45) bins = gpd.GeoDataFrame(geometry=bin_polys) bins[:3] # Make a copy because I'm going to drop points as I # assign them to polys, to speed up subsequent search. midpts = midpoints.copy() offsets, azimuths = [], [] # To hold complete list. # Loop over bin polygons with index i. for i, bin_i in bins.iterrows(): o, a = [], [] # To hold list for this bin only. # Now loop over all midpoints with index j. for j, midpt_j in midpts.iterrows(): if bin_i.geometry.contains(midpt_j.geometry): # Then it's a hit! Add it to the lists, # and drop it so we have less hunting. o.append(midpt_j.offset) a.append(midpt_j.azimuth) midpts = midpts.drop([j]) # Add the bin_i lists to the master list # and go around the outer loop again. offsets.append(o) azimuths.append(a) # Add everything to the dataframe. bins['offsets'] = gpd.GeoSeries(offsets) bins['azimuths'] = gpd.GeoSeries(azimuths) bins[:10] bins['fold'] = bins.apply(lambda row: len(row.offsets), axis=1) ax = bins.plot(column="fold") bins['min_offset'] = bins.apply(lambda row: min(row.offsets) if row.fold > 0 else None, axis=1) ax = bins.plot(column="min_offset")