Source code for pygridtools.iotools

import os
from collections import OrderedDict
from textwrap import dedent
import warnings

import numpy
import pandas
from matplotlib import pyplot
from shapely.geometry import Point, Polygon
import geopandas

try:
    import ipywidgets
except ImportError:  # pragma: no cover
    ipywidgets = None

from pygridgen.tests.utils import requires
import pygridgen as pgg

from pygridtools import misc
from pygridtools import validate
from pygridtools import viz


def _warn_filterfxn(filterfxn):
    msg = '`filterfxn` no longer supported. Use the `query` method of the resulting dataframe.'
    if filterfxn:
        warnings.warn(msg)


[docs]def read_boundary(gisfile, betacol='beta', reachcol=None, sortcol=None, upperleftcol=None, filterfxn=None): """ Loads boundary points from a GIS File. Parameters ---------- gisfile : string Path to the GIS file containaing boundary points. Expected schema of the file... - order: numeric sort order of the points - beta: the 'beta' parameter used in grid generation to define turning points betacol : string (default='beta') Column in the attribute table specifying the beta parameter's value at each point. sortcol : optional string or None (default) Column in the attribute table specifying the sort order of the points. reachcol : optional string or None (default) Column in the attribute table specifying the names of the reaches of the river/esturary system. upperleftcol : optional string or None (default) Column in the attribute table toggling if the a point should be consider the upper-left corner of the system. Only one row of this column should evaluare to True. filterfxn : function or lambda expression or None (default) Removed. Use the `query` method of the result. Returns ------- gdf : geopandas.GeoDataFrame A GeoDataFrame of the boundary points with the following columns: - x (easting) - y (northing) - beta (turning parameter) - order (for sorting) - reach - upperleft """ def _get_col_val(df, col, default=None): if col is not None: return df[col] else: return default _warn_filterfxn(filterfxn) gdf = ( geopandas.read_file(gisfile) .assign(x=lambda df: df['geometry'].x) .assign(y=lambda df: df['geometry'].y) .assign(beta=lambda df: _get_col_val(df, betacol, 0)) .assign(order=lambda df: _get_col_val(df, sortcol, df.index)) .assign(reach=lambda df: _get_col_val(df, reachcol, 'main')) .assign(upperleft=lambda df: _get_col_val(df, upperleftcol, False)) .fillna(0) .sort_values(by=['order']) ) return gdf.loc[:, ['x', 'y', 'beta', 'upperleft', 'reach', 'order', 'geometry']]
[docs]def read_polygons(gisfile, filterfxn=None, squeeze=True, as_gdf=False): """ Load polygons (e.g., water bodies, islands) from a GIS file. Parameters ---------- gisfile : string Path to the gisfile containaing boundary points. filterfxn : function or lambda expression or None (default) Removed. Use the `as_gdf` and the `query` method of the resulting GeoDataFrame. squeeze : optional bool (default = True) Set to True to return an array if only 1 record is present. Otherwise, a list of arrays will be returned. as_gdf : optional bool (default = False) Set to True to return a GeoDataFrame instead of arrays. Returns ------- boundary : array or list of arrays, or GeoDataFrame Notes ----- Multipart geometries are not supported. If a multipart geometry is present in a record, only the first part will be loaded. Z-coordinates are also not supported. Only x-y coordinates will be loaded. """ _warn_filterfxn(filterfxn) gdf = geopandas.read_file(gisfile) if as_gdf: return gdf else: data = [numpy.array(g.boundary.coords) for g in gdf['geometry']] if len(data) == 1 and squeeze: data = data[0] return data
[docs]def read_grid(gisfile, icol='ii', jcol='jj', othercols=None, expand=1, as_gdf=False): if othercols is None: othercols = [] grid = ( geopandas.read_file(gisfile) .rename(columns={icol: 'ii', jcol: 'jj'}) .set_index(['ii', 'jj']) .sort_index() ) if as_gdf: final_cols = othercols + ['geometry'] return grid[final_cols] else: final_cols = ['easting', 'northing'] + othercols if (grid.geom_type != 'Point').any(): msg = "can only read points for now when not returning a geodataframe" raise NotImplementedError(msg) return grid.assign(easting=grid['geometry'].x, northing=grid['geometry'].y)[final_cols]
def _change_shape(g, irows, jcols, plotfxn, plotopts=None): """ changes the number of rows and cols in a Gridgen (g) and passes the grid nodes to a plotting function """ if not plotopts: plotopts = {} g.ny = irows g.nx = jcols g.generate_grid() return plotfxn(g.x, g.y, **plotopts)
[docs]@requires(ipywidgets, 'ipywidgets') def interactive_grid_shape(grid, max_n=200, plotfxn=None, **kwargs): """ Interactive ipywidgets for select the shape of a grid Parameters ---------- grid : pygridgen.Gridgen The base grid from which the grids of new shapes (resolutions) will be generated. max_n : int (default = 200) The maximum number of possible cells in each dimension. plotfxn : callable, optional Function that plots the grid to provide user feedback. The call signature of this function must accept to positional parameters for the x- and y-arrays of node locations, and then accept any remaining keyword arguments. If not provided, *pygridtools.viz.plot_cells* is used. Additional Parameters --------------------- All remaining keyword arguments are passed to *plotfxn* Returns ------- newgrid : pygridgen.Gridgen The reshaped grid widget : ipywidgets.interactive Collection of IntSliders for changing the number cells along each axis in the grid. Examples -------- >>> from pygridgen import grid >>> from pygridtools import viz, iotools >>> def make_fake_bathy(shape): ... j_cells, i_cells = shape ... y, x = numpy.mgrid[:j_cells, :i_cells] ... z = (y - (j_cells // 2))** 2 - x ... return z >>> def plot_grid(x, y, ax=None): ... shape = x[1:, 1:].shape ... bathy = make_fake_bathy(shape) ... if not ax: ... fig, ax = pyplot.subplots(figsize=(8, 8)) ... ax.set_aspect('equal') ... return viz.plot_cells(x, y, ax=ax, cmap='Blues', colors=bathy, lw=0.5, ec='0.3') >>> d = numpy.array([ ... (13, 16, 1.00), (18, 13, 1.00), (12, 7, 0.50), ... (10, 10, -0.25), ( 5, 10, -0.25), ( 5, 0, 1.00), ... ( 0, 0, 1.00), ( 0, 15, 0.50), ( 8, 15, -0.25), ... (11, 13, -0.25)]) >>> g = grid.Gridgen(d[:, 0], d[:, 1], d[:, 2], (75, 75), ul_idx=1, focus=None) >>> new_grid, widget = iotools.interactive_grid_shape(g, plotfxn=plot_grid) """ if not plotfxn: plotfxn = viz.plot_cells common_opts = dict(min=2, max=max_n, continuous_update=False) return grid, ipywidgets.interactive( _change_shape, g=ipywidgets.fixed(grid), irows=ipywidgets.IntSlider(value=grid.ny, **common_opts), jcols=ipywidgets.IntSlider(value=grid.nx, **common_opts), plotfxn=ipywidgets.fixed(plotfxn), plotopts=ipywidgets.fixed(kwargs) )
class _FocusProperties(): """A dummy class to hold the properties of the grid._FocusPoint() object. This class is required so that multiple ipywidgets.interactive widgets can interact on the same plot. """ def __init__(self, pos=0.5, axis='x', factor=0.5, extent=0.5): """ Parameters ---------- pos : float Relative position within the grid of the focus. This must be in the range [0, 1] axis : string ('x' or 'y') Axis along which the grid will be focused. factor : float Amount to focus grid. Creates cell sizes that are factor smaller (factor > 1) or larger (factor < 1) in the focused region. extent : float Lateral extent of focused region.""" self.pos = pos self.axis = axis self.factor = factor self.extent = extent @property def focuspoint(self): """Property returns grid._FocusPoint""" return pgg.grid._FocusPoint(pos=self.pos, axis=self.axis, factor=self.factor, extent=self.extent) def _plot_focus_points(focus_points, g, plotfxn, plotopts=None): """Plots multiple focus points on a grid. Parameters ---------- focus_points : tuple of FocusProperties These focus points are applied to grid `g`. g : grid.Gridgen The grid to plot. plotfxn : callable Function that plots the grid to provide user feedback. The call signature of this function must accept to positional parameters for the x- and y-arrays of node locations, and then accept any remaining keyword arguments. Additional Parameters --------------------- All remaining keyword arguments are passed to *plotfxn* Returns ------- plotfxn(g.x, g.y, **plotopts) """ if not plotopts: plotopts = {} # extact the grid._FocusPoint from the dummy class f = pgg.grid.Focus(*(fp.focuspoint for fp in focus_points)) g.focus = f g.generate_grid() return plotfxn(g.x, g.y, **plotopts) def _change_focus(fpoint, others, axis, pos, factor, extent, g, plotfxn, plotopts=None): """ The function changes the properties of `fpoint` only and plots `fpoint` with `others`. Parameters ---------- fpoint : FocusProperties The focus point modified and applied to grid `g`. others : tuple of FocusProperties These focus points are applied to grid `g` but not modified. axis : string ('x' or 'y') Axis along which the grid will be focused by `fpoint`. pos : float Relative position within the grid of the focus `fpoint`. This must be in the range [0, 1] factor : float Amount to focus grid by `fpoint`. Creates cell sizes that are factor smaller (factor > 1) or larger (factor < 1) in the focused region. extent : float Lateral extent of focused region by `fpoint`. g : grid.Gridgen The grid to plot. plotfxn : callable Function that plots the grid to provide user feedback. The call signature of this function must accept to positional parameters for the x- and y-arrays of node locations, and then accept any remaining keyword arguments. Additional Parameters --------------------- All remaining keyword arguments are passed to *plotfxn* Returns ------- _plot_focus_points(focuspoints, g, plotfxn, plotopts) """ # update fpoint properties fpoint.pos = pos fpoint.axis = axis fpoint.factor = factor fpoint.extent = extent # concat points to plot focuspoints = (fpoint,) + others return _plot_focus_points(focuspoints, g, plotfxn, plotopts) def _plot_points_wrapper(x, y, **kwargs): figsize = kwargs.pop('figsize', (9, 9)) fig, ax = pyplot.subplots(figsize=figsize) ax.set_aspect('equal') return viz.plot_points(x, y, ax=ax, **kwargs)
[docs]@requires(ipywidgets, 'ipywidgets') def interactive_grid_focus(g, n_points, plotfxn=None, **kwargs): """ Interactive ipywidgets for changing focus points. Parameters ---------- grid : pygridgen.Gridgen The base grid from which the grids of new focus points will be generated. n_points : int The number of focal points to add to the grid. plotfxn : callable, optional Function that plots the grid to provide user feedback. The call signature of this function must accept to positional parameters for the x- and y-arrays of node locations, and then accept any remaining keyword arguments. If not provided, *pygridtools.viz.plot_points* is used. Additional Parameters --------------------- All remaining keyword arguments are passed to *plotfxn* Returns ------- newgrid : pygridgen.Gridgen The reshaped grid widget : ipywidgets.interactive Collection of Tab / IntSliders for changing the number focus points along each axis in the grid. Examples -------- >>> from pygridgen import grid >>> from pygridtools import viz, iotools >>> d = numpy.array([ ... (13, 16, 1.00), (18, 13, 1.00), (12, 7, 0.50), ... (10, 10, -0.25), ( 5, 10, -0.25), ( 5, 0, 1.00), ... ( 0, 0, 1.00), ( 0, 15, 0.50), ( 8, 15, -0.25), ... (11, 13, -0.25)]) >>> g = grid.Gridgen(d[:, 0], d[:, 1], d[:, 2], (75, 75), ul_idx=1, focus=None) >>> n = 4 # number of focus objects >>> new_grid, widget = iotools.interactive_grid_focus(g, n) """ if not plotfxn: plotfxn = _plot_points_wrapper # common linear slider options common_opts = dict(min=0.01, max=1, step=0.01, continuous_update=False) # common log slider options common_log_f_opts = dict(min=-2, max=2, step=0.1, continuous_update=False) # we need to create a list of widgets for ipywidgets.Tab() widgets = [] # set up our dummy classes focus_points = [_FocusProperties() for f in range(n_points)] for n, fp in enumerate(focus_points): widget = ipywidgets.interactive( _change_focus, fpoint=ipywidgets.fixed(focus_points[n]), others=ipywidgets.fixed(tuple(focus_points[:n] + focus_points[n + 1:])), axis=ipywidgets.ToggleButtons( options=['x', 'y'], description='Axis:'), pos=ipywidgets.FloatSlider(0.5, **common_opts), # currently the only log slider factor=ipywidgets.FloatLogSlider(1, **common_log_f_opts), extent=ipywidgets.FloatSlider(0.5, **common_opts), g=ipywidgets.fixed(g), plotfxn=ipywidgets.fixed(plotfxn), plotopts=ipywidgets.fixed(kwargs) ) widgets += [widget] tab_nest = ipywidgets.Tab() tab_nest.children = widgets for n in range(len(widgets)): tab_nest.set_title(n, 'Focus {}'.format(n + 1)) return focus_points, tab_nest