Source code for kite.quadtree

import numpy as num
import time
from pyrocko import guts
from pyrocko import orthodrome as od

from .util import Subject, property_cached, derampMatrix


[docs]class QuadNode(object): """ A node (or *tile*) in held by :class:`~kite.Quadtree`. Each node in the tree hold a back reference to the quadtree and scene to access :param llr: Lower left corner row in :attr:`kite.Scene.displacement` matrix. :type llr: int :param llc: Lower left corner column in :attr:`kite.Scene.displacement` matrix. :type llc: int :param length: Length of node in from ``llr, llc`` in both dimensions :type length: int :param id: Unique id of node :type id: str :param children: Node's children :type children: List of :class:`~kite.quadtree.QuadNode` """ CORNERS = (0, 0), (0, 1), (1, 0), (1, 1) MIN_PIXEL_LENGTH_NODE = 4 def __init__(self, quadtree, llr, llc, length): self.children = [] self.llr = int(llr) self.llc = int(llc) self.length = int(length) self._slice_rows = slice(self.llr, self.llr + self.length) self._slice_cols = slice(self.llc, self.llc + self.length) self.id = 'node_%d-%d_%d' % (self.llr, self.llc, self.length) self.quadtree = quadtree self.scene = quadtree.scene self.frame = quadtree.frame @property_cached def nan_fraction(self): """ Fraction of NaN values within the tile :type: float """ return float(num.sum(self.displacement_mask)) / \ self.displacement.size @property_cached def npixel(self): return self.displacement.size @property_cached def mean(self): """ Mean displacement :type: float """ return float(num.nanmean(self.displacement)) @property_cached def median(self): """ Median displacement :type: float """ return float(num.nanmedian(self.displacement)) @property_cached def std(self): """ Standard deviation of displacement :type: float """ return float(num.nanstd(self.displacement)) @property_cached def var(self): """ Variance of displacement :type: float """ return float(num.nanvar(self.displacement)) @property_cached def mean_px_var(self): """ Variance of displacement :type: float """ if self.displacement_px_var is not None: return float(num.nanmean(self.displacement_px_var)) return None @property_cached def corr_median(self): """ Standard deviation of node's displacement corrected for median :type: float """ return float(num.nanstd(self.displacement - self.median)) @property_cached def corr_mean(self): """ Standard deviation of node's displacement corrected for mean :type: float """ return float(num.nanstd(self.displacement - self.mean)) @property_cached def corr_bilinear(self): """ Standard deviation of node's displacement corrected for bilinear trend (2D) :type: float """ return float(num.nanstd(derampMatrix(self.displacement))) @property def weight(self): """ :getter: Absolute weight derived from :class:`kite.Covariance` - works on tree leaves only. :type: float """ return float(self.quadtree.scene.covariance.getLeafWeight(self)) @property_cached def focal_point(self): """ Node focal point in local coordinates respecting NaN values :type: tuple, float - (easting, northing) """ E = float(num.mean(self.gridE.compressed()) + self.frame.dE/2) N = float(num.mean(self.gridN.compressed()) + self.frame.dN/2) return E, N @property_cached def focal_point_meter(self): """ Node focal point in local coordinates respecting NaN values :type: tuple, float - (easting, northing) """ E = float(num.mean(self.gridEmeter.compressed() + self.frame.dEmeter/2)) N = float(num.mean(self.gridNmeter.compressed() + self.frame.dNmeter/2)) return E, N @property_cached def displacement(self): """ Displacement array, slice from :attr:`kite.Scene.displacement` :type: :class:`numpy.ndarray` """ return self.scene.displacement[self._slice_rows, self._slice_cols] @property_cached def displacement_masked(self): """ Masked displacement, see :attr:`~kite.quadtree.QuadNode.displacement` :type: :class:`numpy.ndarray` """ return num.ma.masked_array(self.displacement, self.displacement_mask, fill_value=num.nan) @property_cached def displacement_mask(self): """ Displacement nan mask of :attr:`~kite.quadtree.QuadNode.displacement` :type: :class:`numpy.ndarray`, dtype :class:`numpy.bool` .. todo :: Faster to slice Scene.displacement_mask? """ return num.isnan(self.displacement) @property_cached def displacement_px_var(self): """ Displacement array, slice from :attr:`kite.Scene.displacement` :type: :class:`numpy.ndarray` """ if self.scene.displacement_px_var is not None: return self.scene.displacement_px_var[ self._slice_rows, self._slice_cols] return None @property_cached def phi(self): """ Median Phi angle, see :class:`~kite.Scene`. :type: float """ phi = self.scene.phi[self._slice_rows, self._slice_cols] return num.nanmedian(phi[~self.displacement_mask]) @property_cached def theta(self): """ Median Theta angle, see :class:`~kite.Scene`. :type: float """ theta = self.scene.theta[self._slice_rows, self._slice_cols] return num.nanmedian(theta[~self.displacement_mask]) @property_cached def gridE(self): """ Grid holding local east coordinates, see :attr:`kite.scene.Frame.gridE`. :type: :class:`numpy.ndarray` """ return self.scene.frame.gridE[self._slice_rows, self._slice_cols] @property_cached def gridEmeter(self): """ Grid holding local east coordinates, see :attr:`kite.scene.Frame.gridEmeter`. :type: :class:`numpy.ndarray` """ return self.scene.frame.gridEmeter[self._slice_rows, self._slice_cols] @property_cached def gridN(self): """ Grid holding local north coordinates, see :attr:`kite.scene.Frame.gridN`. :type: :class:`numpy.ndarray` """ return self.scene.frame.gridN[self._slice_rows, self._slice_cols] @property_cached def gridNmeter(self): """ Grid holding local north coordinates, see :attr:`kite.scene.Frame.gridNmeter`. :type: :class:`numpy.ndarray` """ return self.scene.frame.gridNmeter[self._slice_rows, self._slice_cols] @property def llE(self): """ :getter: Lower left east coordinate in local coordinates (*meters* or *degree*). :type: float """ return self.scene.frame.E[self.llc] @property def llN(self): """ :getter: Lower left north coordinate in local coordinates (*meter* or *degree*). :type: float """ return self.scene.frame.N[self.llr] @property def urN(self): return self.llN + self.sizeN @property def urE(self): return self.llE + self.sizeE @property_cached def sizeE(self): """ :getter: Size in eastern direction in *meters* or *degree*. :type: float """ sizeE = self.length * self.scene.frame.dE if (self.llE + sizeE) > self.scene.frame.E.max(): sizeE = self.scene.frame.E.max() - self.llE return sizeE @property_cached def sizeN(self): """ :getter: Size in northern direction in *meters* or *degree*. :type: float """ sizeN = self.length * self.scene.frame.dN if (self.llN + sizeN) > self.scene.frame.N.max(): sizeN = self.scene.frame.N.max() - self.llN return sizeN
[docs] def iterChildren(self): """ Iterator over the all children. :yields: Children of it's own. :type: :class:`~kite.quadtree.QuadNode` """ yield self if self.children is not None: for c in self.children: yield from c.iterChildren()
[docs] def iterLeaves(self): """ Iterator over the leaves, evaluating parameters from :class:`~kite.Quadtree` instance. :yields: Leafs fullfilling the tree's parameters. :type: :class:`~kite.quadtree.QuadNode` """ if (self.quadtree._corr_func(self) < self.quadtree.epsilon and not self.length > self.quadtree._tile_size_lim_px[1])\ or self.children is None \ or (self.children[0].length < self.quadtree._tile_size_lim_px[0]): yield self else: for c in self.children: yield from c.iterLeaves()
def _iterSplitNode(self): if self.length == 1: yield None for nr, nc in self.CORNERS: n = QuadNode(self.quadtree, self.llr + self.length / 2 * nr, self.llc + self.length / 2 * nc, self.length / 2) if n.displacement.size == 0 or num.all(n.displacement_mask): continue yield n
[docs] def createTree(self): """ Create the tree from a set of basenodes, ignited by :class:`~kite.Quadtree` instance. Evaluates :class:`~kite.Quadtree` correction method and :attr:`~kite.Quadtree.epsilon_min`. """ if (self.quadtree._corr_func(self) > self.quadtree.epsilon_min or self.length >= 64)\ and not self.length < self.MIN_PIXEL_LENGTH_NODE: # self.length > .1 * max(self.quadtree._data.shape): !! Expensive self.children = tuple(c for c in self._iterSplitNode()) if len(self.children) == 0: self.children = None else: for c in self.children: c.createTree() else: self.children = None
[docs]class QuadtreeConfig(guts.Object): """ Quadtree configuration object holding essential parameters used to reconstruct a particular tree """ correction = guts.StringChoice.T( choices=('mean', 'median', 'bilinear', 'std'), default='median', help='Node correction for splitting, available methods ' ' ``[\'mean\', \'median\', \'bilinear\', \'std\']``') epsilon = guts.Float.T( optional=True, help='Variance threshold when a node is split') nan_allowed = guts.Float.T( default=0.9, help='Allowed NaN fraction per tile') tile_size_min = guts.Float.T( optional=True, help='Minimum allowed tile size in *meters* or *degree*') tile_size_max = guts.Float.T( optional=True, help='Maximum allowed tile size in *meters* or *degree*') leaf_blacklist = guts.List.T( optional=True, default=[], help='Blacklist of excluded leaves')
[docs]class Quadtree(object): """Quadtree for irregular subsampling InSAR displacement data held in :py:class:`kite.scene.Scene` InSAR displacement scenes can hold a vast amount of data points, which is often highly redundant and unsuitably large for the use in inverse modeling. By subsampling and therefore decimating the data points systematically through a parametrized quadtree we can reduce the dataset without significant loss of displacement information. Quadtree subsampling keeps a high spatial resolution where displacement gradients are high and efficiently reduces data point density in regions with small displacement variations. The product is a managable dataset size with good representation of the original data. The standard deviation from :attr:`kite.quadtree.QuadNode.displacement` is evaluated against different corrections: * ``mean``: Mean is substracted * ``median``: Median is substracted * ``bilinear``: A 2D detrend is applied to the node * ``std``: Pure standard deviation without correction set through :func:`~kite.Quadtree.setCorrection`. If the standard deviation exceeds :attr:`~kite.Quadtree.epsilon` the node is split. The leaves can also be exported in a *CSV* format by :func:`~kite.Quadtree.export_csv`, or *GeoJSON* by :func:`~kite.Quadtree.export_geojson`. Controlling attributes are: * :attr:`~kite.Quadtree.epsilon`, RMS threshold * :attr:`~kite.Quadtree.nan_fraction`, allowed :attr:`numpy.nan` in node * :attr:`~kite.Quadtree.tile_size_max`, maximum node size in *meters* or *degree* * :attr:`~kite.Quadtree.tile_size_min`, minimum node size in *meter* or *degree* :attr:`~kite.Quadtree.leaves` hold the current tree's :class:`~kite.quadtree.QuadNode` 's. """ _displacement_corrections = { 'mean': ('Standard deviation around mean', lambda n: n.corr_mean), 'median': ('Standard deviation around median', lambda n: n.corr_median), 'bilinear': ('Standard deviation around bilinear detrended node', lambda n: n.corr_bilinear), 'std': ('Standard deviation (std)', lambda n: n.std), } _norm_methods = { 'mean': lambda n: n.mean, 'median': lambda n: n.median, 'weight': lambda n: n.weight, } def __init__(self, scene, config=QuadtreeConfig()): self.evChanged = Subject() self.evConfigChanged = Subject() self._leaves = None self.scene = scene self.displacement = self.scene.displacement self.frame = self.scene.frame # Cached matrices self._leaf_matrix_means = num.empty_like(self.displacement) self._leaf_matrix_medians = num.empty_like(self.displacement) self._leaf_matrix_weights = num.empty_like(self.displacement) self._log = scene._log.getChild('Quadtree') self.setConfig(config) self.scene.evConfigChanged.subscribe(self.setConfig)
[docs] def setConfig(self, config=None): """ Sets and updated the config of the instance :param config: New config instance, defaults to configuration provided by parent :class:`~kite.Scene` :type config: :class:`~kite.covariance.QuadtreeConfig`, optional """ if config is None: config = self.scene.config.quadtree if self.scene.config.old_import: frame = self.scene.config.frame from pyrocko import orthodrome as od self._log.warning('Old format - converting quadtree configuration') dLat, dLon = od.ne_to_latlon( frame.llLat, frame.llLon, config.tile_size_max, config.tile_size_min) config.tile_size_min = dLon - frame.llLon config.tile_size_max = dLat - frame.llLat self.config = config self.setCorrection(self.config.correction) self.evConfigChanged.notify()
[docs] def setCorrection(self, correction='mean'): """ Set correction method calculating the standard deviation of instances :class:`~kite.quadtree.QuadNode` s The standard deviation from :attr:`kite.quadtree.QuadNode.displacement` is evaluated against different corrections: * ``mean``: Mean is substracted * ``median``: Median is substracted * ``bilinear``: A 2D detrend is applied to the node * ``std``: Pure standard deviation without correction :param correction: Choose from methods ``mean_std, median_std, bilinear_std, std`` :type correction: str :raises: AttributeError """ if correction not in self._displacement_corrections.keys(): raise AttributeError('Method %s not in %s', correction, self._displacement_corrections) self._log.debug('Changing to split method \'%s\'', correction) self.config.correction = correction self._corr_func = self._displacement_corrections[correction][1] # Clearing cached properties through None self.leaf_center_distance = None self.nodes = None self.epsilon_min = None self._epsilon_init = None self.clearLeaves() self.epsilon = self.config.epsilon or self._epsilon_init self._initTree() if self.nleaves == 0: self._log.warning('No leaves in default quadtree,' ' setting allowed_nan=1.') self.nan_allowed = 1. self.evChanged.notify()
[docs] def clearLeaves(self): """Clear cached leafs and properties""" self.leaves = None self.leaf_center_distance = None self.leaf_los_rotation_factors = None self.leaf_means = None self.leaf_medians = None
@property def min_node_length_px(self): npx = max(self.frame.cols, self.frame.rows) return int(2**round(num.log(npx / 64))) def _initTree(self): QuadNode.MIN_PIXEL_LENGTH_NODE = self.min_node_length_px t0 = time.time() for b in self._base_nodes: b.createTree() self._log.debug('Tree created, %d nodes [%0.4f s]', self.nnodes, time.time() - t0) @property def epsilon(self): """ Threshold for quadtree splitting its ``QuadNode``. The threshold is the maximum standard deviation of leaf mean, median or simply its values (see ''SetSplitMethod'') allowed to not further split a "QuadNode". :setter: Sets the epsilon/RMS threshold :getter: Returns the current epsilon :type: float """ return self.config.epsilon @epsilon.setter def epsilon(self, value): value = float(value) if self.config.epsilon == value: return if value < self.epsilon_min: self._log.warning( 'Epsilon is out of bounds [%0.6f], epsilon_min %0.6f', value, self.epsilon_min) return self.clearLeaves() self.clearLeafBlacklist() self.config.epsilon = value self.evChanged.notify() @property_cached def _epsilon_init(self): """ Initial epsilon for virgin tree creation """ return num.nanstd(self.displacement) @property_cached def epsilon_min(self): """ Lowest allowed epsilon :type: float """ return self._epsilon_init * .1 @property def nan_allowed(self): """Fraction of allowed ``NaN`` values in quadtree leaves. If value is exceeded the leaf is kicked out entirely. :setter: Fraction ``0. <= fraction <= 1``. :type: float """ return self.config.nan_allowed @nan_allowed.setter def nan_allowed(self, value): if (value > 1. or value <= 0.): self._log.warning('NaN fraction must be 0. < nan_allowed <= 1.') return self.clearLeaves() self.clearLeafBlacklist() self.config.nan_allowed = value self.evChanged.notify() @property def tile_size_min(self): """ Minimum allowed tile size in *meter*. Measured along long edge ``(max(dE, dN))``. Minimum tile size defaults to 1/20th of the largest dimension :getter: Returns the minimum allowed tile size :setter: Sets the minimum threshold :type: float """ if self.config.tile_size_min is None: frame = self.scene.frame max_px = max(frame.shape) self.config.tile_size_min = max(frame.dE, frame.dN) * (max_px/20) return self.config.tile_size_min @tile_size_min.setter def tile_size_min(self, value): if value > self.tile_size_max: self._log.warning('tile_size_min > tile_size_max is required!') return self.config.tile_size_min = value self._tileSizeChanged() @property def tile_size_max(self): """ Maximum allowed tile size in *meter*. Measured along long edge ``(max(dE, dN))`` Maximum tile size defaults to 1/5th of the largest dimension :getter: Returns the maximum allowed tile size :setter: Sets the maximum threshold :type: float """ if self.config.tile_size_max is None: frame = self.scene.frame max_px = max(frame.shape) self.config.tile_size_max = max(frame.dE, frame.dN) * (max_px/5) return self.config.tile_size_max @tile_size_max.setter def tile_size_max(self, value): if value < self.tile_size_min: self._log.warning('tile_size_min > tile_size_max is required') return self.config.tile_size_max = value self._tileSizeChanged() def _tileSizeChanged(self): self._tile_size_lim_px = None self.clearLeaves() self.clearLeafBlacklist() self.evChanged.notify() @property_cached def _tile_size_lim_px(self): dpx = max(self.scene.frame.dE, self.scene.frame.dN) return (round(self.tile_size_min / dpx), round(self.tile_size_max / dpx)) @property_cached def nodes(self): """ All nodes of the tree :getter: Get the list of nodes :type: list """ return [n for b in self._base_nodes for n in b.iterChildren()] @property def nnodes(self): """ :getter: Number of nodes of the built tree. :type: int """ return len(self.nodes) def clearLeafBlacklist(self): self.config.leaf_blacklist = []
[docs] def blacklistLeaves(self, leaves): """ Blacklist a leaf and exclude it from the tree :param leaves: Leaf instances :type leaves: list """ self.config.leaf_blacklist.extend(leaves) self._log.debug('Blacklisted leaves: %s' % ', '.join(self.config.leaf_blacklist)) self.clearLeaves() self.evChanged.notify()
@property_cached def leaves(self): """:getter: List of leaves for current configuration. :type: (list or :class:`~kite.quadtree.QuadNode` s) """ t0 = time.time() leaves = [] for b in self._base_nodes: leaves.extend( [lf for lf in b.iterLeaves() if lf.nan_fraction < self.nan_allowed and lf.id not in self.config.leaf_blacklist]) self._log.debug( 'Gathering leaves for epsilon %.4f (nleaves=%d) [%0.4f s]' % (self.epsilon, len(leaves), time.time() - t0)) return leaves @property def nleaves(self): """ :getter: Number of leaves for current parametrisation. :type: int """ return len(self.leaves) @property def leaf_mean_px_var(self): """ :getter: Mean pixel variance in each quadtree, if :attr:`kite.Scene.displacement_px_var` is set. :type: :class:`numpy.ndarray`, size ``N``. """ if self.scene.displacement_px_var is not None: return num.array([lf.mean_px_var for lf in self.leaves]) return None @property_cached def leaf_means(self): """ :getter: Leaf mean displacements from :attr:`kite.quadtree.QuadNode.mean`. :type: :class:`numpy.ndarray`, size ``N``. """ return num.array([lf.mean for lf in self.leaves]) @property_cached def leaf_medians(self): """ :getter: Leaf median displacements from :attr:`kite.quadtree.QuadNode.median`. :type: :class:`numpy.ndarray`, size ``N``. """ return num.array([lf.median for lf in self.leaves]) @property def _leaf_focal_points(self): return num.array([lf._focal_point for lf in self.leaves]) @property def leaf_focal_points(self): """ :getter: Leaf focal points in local coordinates. :type: :class:`numpy.ndarray`, size ``(N, 2)`` """ return num.array([lf.focal_point for lf in self.leaves]) @property def leaf_focal_points_meter(self): """ :getter: Leaf focal points in meter. :type: :class:`numpy.ndarray`, size ``(N, 2)`` """ return num.array([lf.focal_point_meter for lf in self.leaves]) @property def leaf_coordinates(self): """Synonym for :func:`Quadtree.leaf_focal_points` in easting/northing""" return self.leaf_focal_points @property_cached def leaf_center_distance(self): """ :getter: Leaf distance to center point of the quadtree :type: :class:`numpy.ndarray`, size ``(N, 3)`` """ distances = num.empty((self.nleaves, 3)) center = self.center_point distances[:, 0] = self.leaf_focal_points[:, 0] - center[0] distances[:, 1] = self.leaf_focal_points[:, 1] - center[1] distances[:, 2] = num.sqrt(distances[:, 1]**2 + distances[:, 1]**2) return distances @property def leaf_eastings(self): return self.leaf_coordinates[:, 0] @property def leaf_northings(self): return self.leaf_coordinates[:, 1] @property def leaf_phis(self): """ :getter: Median leaf LOS phi angle. :attr:`kite.Scene.phi` :type: :class:`numpy.ndarray`, size ``(N)`` """ return num.array([lf.phi for lf in self.leaves]) @property def leaf_thetas(self): """ :getter: Median leaf LOS theta angle. :attr:`kite.Scene.theta` :type: :class:`numpy.ndarray`, size ``(N)`` """ return num.array([lf.theta for lf in self.leaves]) @property_cached def leaf_los_rotation_factors(self): """ :getter: Trigonometric factors for rotating displacement matrices towards LOS. See :attr:`kite.BaseScene.los_rotation_factors` :type: :class:`numpy.ndarray`, Nx3 """ los_factors = num.empty((self.nleaves, 3)) los_factors[:, 0] = num.sin(self.leaf_thetas) los_factors[:, 1] = num.cos(self.leaf_thetas)\ * num.cos(self.leaf_phis) los_factors[:, 2] = num.cos(self.leaf_thetas)\ * num.sin(self.leaf_phis) return los_factors @property def leaf_matrix_means(self): """ :getter: Leaf mean displacements casted to :attr:`kite.Scene.displacement`. :type: :class:`numpy.ndarray`, size ``(N, M)`` """ return self._getLeafsNormMatrix(self._leaf_matrix_means, method='mean') @property def leaf_matrix_medians(self): """ :getter: Leaf median displacements casted to :attr:`kite.Scene.displacement`. :type: :class:`numpy.ndarray`, size ``(N, M)`` """ return self._getLeafsNormMatrix(self._leaf_matrix_medians, method='median') @property def leaf_matrix_weights(self): """ :getter: Leaf weights casted to :attr:`kite.Scene.displacement`. :type: :class:`numpy.ndarray`, size ``(N, M)`` """ return self._getLeafsNormMatrix(self._leaf_matrix_weights, method='weight') def _getLeafsNormMatrix(self, array, method='median'): if method not in self._norm_methods.keys(): raise AttributeError( 'Method %s is not in %s' % (method, list(self._norm_methods.keys()))) array.fill(num.nan) for lf in self.leaves: array[lf._slice_rows, lf._slice_cols] = \ self._norm_methods[method](lf) array[self.scene.displacement_mask] = num.nan return array @property def center_point(self): return num.median(self.leaf_focal_points, axis=0) @property def reduction_efficiency(self): """ This is measure for the reduction of the scene's full resolution over the quadtree. :getter: Quadtree efficiency as :math:`N_{full} / N_{leaves}` :type: float """ return (self.scene.rows * self.scene.cols) / \ (self.nleaves if self.nleaves else 1) @property def reduction_rms(self): """ The RMS error is defined between :attr:`~kite.Quadtree.leaf_matrix_means` and :attr:`kite.Scene.displacement`. :getter: The reduction RMS error :type: float """ if num.all(num.isnan(self.leaf_matrix_means)): return num.inf return num.sqrt(num.nanmean((self.scene.displacement - self.leaf_matrix_means)**2)) @property_cached def _base_nodes(self): self._base_nodes = [] init_length = num.power( 2, num.ceil(num.log(num.min(self.displacement.shape)) / num.log(2))) nx, ny = num.ceil(num.array(self.displacement.shape) / init_length) self._log.debug('Creating %d base nodes', nx * ny) for ir in range(int(nx)): for ic in range(int(ny)): llr = ir * init_length llc = ic * init_length self._base_nodes.append(QuadNode(self, llr, llc, init_length)) if len(self._base_nodes) == 0: raise AssertionError('Could not init base nodes.') return self._base_nodes @property_cached def plot(self): """ Simple `matplotlib` illustration of the quadtree :type: :attr:`Quadtree.leaf_matrix_means`. """ from kite.plot2d import QuadtreePlot return QuadtreePlot(self)
[docs] def getStaticTarget(self): """Not Implemented """ raise NotImplementedError
[docs] def getMPLRectangles(self): """ Get the quadtree as a list of matplotlib rectangles. :returns: Rectangles for plotting :rtype: list of :class:`matplotlib.patcjes.Rectangle` """ from matplotlib.patches import Rectangle rectangles = [] for lf in self.leaves: r = Rectangle((lf.llE, lf.llN), lf.sizeE, lf.sizeN) rectangles.append(r) return rectangles
[docs] def export_csv(self, filename): """ Exports the current quadtree leaves to ``filename`` in a *CSV* format The formatting is:: # node_id, focal_point_E, focal_point_N, theta, phi, \ mean_displacement, median_displacement, absolute_weight :param filename: export_csv to path :type filename: string """ self._log.debug('Exporting Quadtree as to %s', filename) with open(filename, mode='w') as f: f.write( '# node_id, focal_point_E, focal_point_N, theta, phi, ' 'mean_displacement, median_displacement, absolute_weight\n') for lf in self.leaves: f.write( '{lf.id}, {lf.focal_point[0]}, {lf.focal_point[1]}, ' '{lf.theta}, {lf.phi}, ' '{lf.mean}, {lf.median}, {lf.weight}\n'.format(lf=lf))
def export_geojson(self, filename): import geojson self._log.debug('Exporting GeoJSON Quadtree to %s', filename) features = [] for lf in self.leaves: llN, llE, urN, urE = (lf.llN, lf.llE, lf.urN, lf.urE) if self.frame.isDegree(): llN += self.frame.llLat llE += self.frame.llLon urN += self.frame.llLat urE += self.frame.llLon coords = num.array([ (llN, llE), (llN, urE), (urN, urE), (urN, llE), (llN, llE)]) if self.frame.isMeter(): coords = od.ne_to_latlon( self.frame.llLat, self.frame.llLon, *coords.T) coords = num.array(coords).T coords = coords[:, [1, 0]].tolist() feature = geojson.Feature( geometry=geojson.Polygon(coordinates=[coords]), id=lf.id, properties={ 'mean': lf.mean, 'median': lf.median, 'std': lf.std, 'var': lf.var }) features.append(feature) collection = geojson.FeatureCollection( features) with open(filename, 'w') as f: geojson.dump(collection, f)
__all__ = ['Quadtree', 'QuadtreeConfig'] if __name__ == '__main__': from kite.scene import SceneSynTest sc = SceneSynTest.createGauss(2000, 2000) for e in num.linspace(0.1, .00005, num=30): sc.quadtree.epsilon = e # qp = Plot2DQuadTree(qt, cmap='spectral') # qp.plot()