PostGIS  2.3.8dev-r@@SVN_REVISION@@

◆ compute_gserialized_stats_mode()

static void compute_gserialized_stats_mode ( VacAttrStats *  stats,
AnalyzeAttrFetchFunc  fetchfunc,
int  sample_rows,
double  total_rows,
int  mode 
)
static

The gserialized_analyze_nd sets this function as a callback on the stats object when called by the ANALYZE command.

ANALYZE then gathers the requisite number of sample rows and then calls this function.

We could also pass stats->extra_data in from gserialized_analyze_nd (things like the column type or other stuff from the system catalogs) but so far we don't use that capability.

Our job is to build some statistics on the sample data for use by operator estimators.

We will populate an n-d histogram using the provided sample rows. The selectivity estimators (sel and j_oinsel) can then use the histogram

Definition at line 1297 of file gserialized_estimate.c.

References ND_STATS_T::cells_covered, ND_STATS_T::extent, gbox_is_valid(), gbox_ndims(), gserialized_get_gbox_p(), ND_STATS_T::histogram_cells, ND_STATS_T::histogram_features, LW_FAILURE, ND_BOX_T::max, ND_IBOX_T::max, ND_BOX_T::min, ND_IBOX_T::min, GBOX::mmax, GBOX::mmin, nd_box_array_distribution(), nd_box_expand(), nd_box_from_gbox(), nd_box_init(), nd_box_init_bounds(), nd_box_intersects(), nd_box_merge(), nd_box_overlap(), nd_box_ratio(), nd_box_to_json(), ND_DIMS, nd_increment(), nd_stats_to_json(), nd_stats_value_index(), ND_STATS_T::ndims, ND_STATS_T::not_null_features, ND_STATS_T::sample_features, SDFACTOR, ND_STATS_T::size, STATISTIC_KIND_2D, STATISTIC_KIND_ND, STATISTIC_SLOT_2D, STATISTIC_SLOT_ND, ND_STATS_T::table_features, total_double(), ND_STATS_T::value, GBOX::zmax, and GBOX::zmin.

Referenced by compute_gserialized_stats().

1299 {
1300  MemoryContext old_context;
1301  int d, i; /* Counters */
1302  int notnull_cnt = 0; /* # not null rows in the sample */
1303  int null_cnt = 0; /* # null rows in the sample */
1304  int histogram_features = 0; /* # rows that actually got counted in the histogram */
1305 
1306  ND_STATS *nd_stats; /* Our histogram */
1307  size_t nd_stats_size; /* Size to allocate */
1308 
1309  double total_width = 0; /* # of bytes used by sample */
1310  double total_sample_volume = 0; /* Area/volume coverage of the sample */
1311  double total_cell_count = 0; /* # of cells in histogram affected by sample */
1312 
1313  ND_BOX sum; /* Sum of extents of sample boxes */
1314  ND_BOX avg; /* Avg of extents of sample boxes */
1315  ND_BOX stddev; /* StdDev of extents of sample boxes */
1316 
1317  const ND_BOX **sample_boxes; /* ND_BOXes for each of the sample features */
1318  ND_BOX sample_extent; /* Extent of the raw sample */
1319  int histo_size[ND_DIMS]; /* histogram nrows, ncols, etc */
1320  ND_BOX histo_extent; /* Spatial extent of the histogram */
1321  ND_BOX histo_extent_new; /* Temporary variable */
1322  int histo_cells_target; /* Number of cells we will shoot for, given the stats target */
1323  int histo_cells; /* Number of cells in the histogram */
1324  int histo_cells_new = 1; /* Temporary variable */
1325 
1326  int ndims = 2; /* Dimensionality of the sample */
1327  int histo_ndims = 0; /* Dimensionality of the histogram */
1328  double sample_distribution[ND_DIMS]; /* How homogeneous is distribution of sample in each axis? */
1329  double total_distribution; /* Total of sample_distribution */
1330 
1331  int stats_slot; /* What slot is this data going into? (2D vs ND) */
1332  int stats_kind; /* And this is what? (2D vs ND) */
1333 
1334  /* Initialize sum and stddev */
1335  nd_box_init(&sum);
1336  nd_box_init(&stddev);
1337 
1338  /*
1339  * This is where gserialized_analyze_nd
1340  * should put its' custom parameters.
1341  */
1342  /* void *mystats = stats->extra_data; */
1343 
1344  POSTGIS_DEBUG(2, "compute_gserialized_stats called");
1345  POSTGIS_DEBUGF(3, " # sample_rows: %d", sample_rows);
1346  POSTGIS_DEBUGF(3, " estimate of total_rows: %.6g", total_rows);
1347 
1348  /*
1349  * We might need less space, but don't think
1350  * its worth saving...
1351  */
1352  sample_boxes = palloc(sizeof(ND_BOX*) * sample_rows);
1353 
1354  /*
1355  * First scan:
1356  * o read boxes
1357  * o find dimensionality of the sample
1358  * o find extent of the sample
1359  * o count null-infinite/not-null values
1360  * o compute total_width
1361  * o compute total features's box area (for avgFeatureArea)
1362  * o sum features box coordinates (for standard deviation)
1363  */
1364  for ( i = 0; i < sample_rows; i++ )
1365  {
1366  Datum datum;
1367  GSERIALIZED *geom;
1368  GBOX gbox;
1369  ND_BOX *nd_box;
1370  bool is_null;
1371  bool is_copy;
1372 
1373  datum = fetchfunc(stats, i, &is_null);
1374 
1375  /* Skip all NULLs. */
1376  if ( is_null )
1377  {
1378  POSTGIS_DEBUGF(4, " skipped null geometry %d", i);
1379  null_cnt++;
1380  continue;
1381  }
1382 
1383  /* Read the bounds from the gserialized. */
1384  geom = (GSERIALIZED *)PG_DETOAST_DATUM(datum);
1385  is_copy = VARATT_IS_EXTENDED(datum);
1386  if ( LW_FAILURE == gserialized_get_gbox_p(geom, &gbox) )
1387  {
1388  /* Skip empties too. */
1389  POSTGIS_DEBUGF(3, " skipped empty geometry %d", i);
1390  continue;
1391  }
1392 
1393  /* If we're in 2D mode, zero out the higher dimensions for "safety" */
1394  if ( mode == 2 )
1395  gbox.zmin = gbox.zmax = gbox.mmin = gbox.mmax = 0.0;
1396 
1397  /* Check bounds for validity (finite and not NaN) */
1398  if ( ! gbox_is_valid(&gbox) )
1399  {
1400  POSTGIS_DEBUGF(3, " skipped infinite/nan geometry %d", i);
1401  continue;
1402  }
1403 
1404  /*
1405  * In N-D mode, set the ndims to the maximum dimensionality found
1406  * in the sample. Otherwise, leave at ndims == 2.
1407  */
1408  if ( mode != 2 )
1409  ndims = Max(gbox_ndims(&gbox), ndims);
1410 
1411  /* Convert gbox to n-d box */
1412  nd_box = palloc(sizeof(ND_BOX));
1413  nd_box_from_gbox(&gbox, nd_box);
1414 
1415  /* Cache n-d bounding box */
1416  sample_boxes[notnull_cnt] = nd_box;
1417 
1418  /* Initialize sample extent before merging first entry */
1419  if ( ! notnull_cnt )
1420  nd_box_init_bounds(&sample_extent);
1421 
1422  /* Add current sample to overall sample extent */
1423  nd_box_merge(nd_box, &sample_extent);
1424 
1425  /* How many bytes does this sample use? */
1426  total_width += VARSIZE(geom);
1427 
1428  /* Add bounds coordinates to sums for stddev calculation */
1429  for ( d = 0; d < ndims; d++ )
1430  {
1431  sum.min[d] += nd_box->min[d];
1432  sum.max[d] += nd_box->max[d];
1433  }
1434 
1435  /* Increment our "good feature" count */
1436  notnull_cnt++;
1437 
1438  /* Free up memory if our sample geometry was copied */
1439  if ( is_copy )
1440  pfree(geom);
1441 
1442  /* Give backend a chance of interrupting us */
1443  vacuum_delay_point();
1444  }
1445 
1446  /*
1447  * We'll build a histogram having stats->attr->attstattarget cells
1448  * on each side, within reason... we'll use ndims*10000 as the
1449  * maximum number of cells.
1450  * Also, if we're sampling a relatively small table, we'll try to ensure that
1451  * we have an average of 5 features for each cell so the histogram isn't
1452  * so sparse.
1453  */
1454  histo_cells_target = (int)pow((double)(stats->attr->attstattarget), (double)ndims);
1455  histo_cells_target = Min(histo_cells_target, ndims * 10000);
1456  histo_cells_target = Min(histo_cells_target, (int)(total_rows/5));
1457  POSTGIS_DEBUGF(3, " stats->attr->attstattarget: %d", stats->attr->attstattarget);
1458  POSTGIS_DEBUGF(3, " target # of histogram cells: %d", histo_cells_target);
1459 
1460  /* If there's no useful features, we can't work out stats */
1461  if ( ! notnull_cnt )
1462  {
1463  elog(NOTICE, "no non-null/empty features, unable to compute statistics");
1464  stats->stats_valid = false;
1465  return;
1466  }
1467 
1468  POSTGIS_DEBUGF(3, " sample_extent: %s", nd_box_to_json(&sample_extent, ndims));
1469 
1470  /*
1471  * Second scan:
1472  * o compute standard deviation
1473  */
1474  for ( d = 0; d < ndims; d++ )
1475  {
1476  /* Calculate average bounds values */
1477  avg.min[d] = sum.min[d] / notnull_cnt;
1478  avg.max[d] = sum.max[d] / notnull_cnt;
1479 
1480  /* Calculate standard deviation for this dimension bounds */
1481  for ( i = 0; i < notnull_cnt; i++ )
1482  {
1483  const ND_BOX *ndb = sample_boxes[i];
1484  stddev.min[d] += (ndb->min[d] - avg.min[d]) * (ndb->min[d] - avg.min[d]);
1485  stddev.max[d] += (ndb->max[d] - avg.max[d]) * (ndb->max[d] - avg.max[d]);
1486  }
1487  stddev.min[d] = sqrt(stddev.min[d] / notnull_cnt);
1488  stddev.max[d] = sqrt(stddev.max[d] / notnull_cnt);
1489 
1490  /* Histogram bounds for this dimension bounds is avg +/- SDFACTOR * stdev */
1491  histo_extent.min[d] = Max(avg.min[d] - SDFACTOR * stddev.min[d], sample_extent.min[d]);
1492  histo_extent.max[d] = Min(avg.max[d] + SDFACTOR * stddev.max[d], sample_extent.max[d]);
1493  }
1494 
1495  /*
1496  * Third scan:
1497  * o skip hard deviants
1498  * o compute new histogram box
1499  */
1500  nd_box_init_bounds(&histo_extent_new);
1501  for ( i = 0; i < notnull_cnt; i++ )
1502  {
1503  const ND_BOX *ndb = sample_boxes[i];
1504  /* Skip any hard deviants (boxes entirely outside our histo_extent */
1505  if ( ! nd_box_intersects(&histo_extent, ndb, ndims) )
1506  {
1507  POSTGIS_DEBUGF(4, " feature %d is a hard deviant, skipped", i);
1508  sample_boxes[i] = NULL;
1509  continue;
1510  }
1511  /* Expand our new box to fit all the other features. */
1512  nd_box_merge(ndb, &histo_extent_new);
1513  }
1514  /*
1515  * Expand the box slightly (1%) to avoid edge effects
1516  * with objects that are on the boundary
1517  */
1518  nd_box_expand(&histo_extent_new, 0.01);
1519  histo_extent = histo_extent_new;
1520 
1521  /*
1522  * How should we allocate our histogram cells to the
1523  * different dimensions? We can't do it by raw dimensional width,
1524  * because in x/y/z space, the z can have different units
1525  * from the x/y. Similarly for x/y/t space.
1526  * So, we instead calculate how much features overlap
1527  * each other in their dimension to figure out which
1528  * dimensions have useful selectivity characteristics (more
1529  * variability in density) and therefor would find
1530  * more cells useful (to distinguish between dense places and
1531  * homogeneous places).
1532  */
1533  nd_box_array_distribution(sample_boxes, notnull_cnt, &histo_extent, ndims,
1534  sample_distribution);
1535 
1536  /*
1537  * The sample_distribution array now tells us how spread out the
1538  * data is in each dimension, so we use that data to allocate
1539  * the histogram cells we have available.
1540  * At this point, histo_cells_target is the approximate target number
1541  * of cells.
1542  */
1543 
1544  /*
1545  * Some dimensions have basically a uniform distribution, we want
1546  * to allocate no cells to those dimensions, only to dimensions
1547  * that have some interesting differences in data distribution.
1548  * Here we count up the number of interesting dimensions
1549  */
1550  for ( d = 0; d < ndims; d++ )
1551  {
1552  if ( sample_distribution[d] > 0 )
1553  histo_ndims++;
1554  }
1555 
1556  if ( histo_ndims == 0 )
1557  {
1558  /* Special case: all our dimensions had low variability! */
1559  /* We just divide the cells up evenly */
1560  POSTGIS_DEBUG(3, " special case: no axes have variability");
1561  histo_cells_new = 1;
1562  for ( d = 0; d < ndims; d++ )
1563  {
1564  histo_size[d] = 1 + (int)pow((double)histo_cells_target, 1/(double)ndims);
1565  POSTGIS_DEBUGF(3, " histo_size[d]: %d", histo_size[d]);
1566  histo_cells_new *= histo_size[d];
1567  }
1568  POSTGIS_DEBUGF(3, " histo_cells_new: %d", histo_cells_new);
1569  }
1570  else
1571  {
1572  /*
1573  * We're going to express the amount of variability in each dimension
1574  * as a proportion of the total variability and allocate cells in that
1575  * dimension relative to that proportion.
1576  */
1577  POSTGIS_DEBUG(3, " allocating histogram axes based on axis variability");
1578  total_distribution = total_double(sample_distribution, ndims); /* First get the total */
1579  POSTGIS_DEBUGF(3, " total_distribution: %.8g", total_distribution);
1580  histo_cells_new = 1; /* For the number of cells in the final histogram */
1581  for ( d = 0; d < ndims; d++ )
1582  {
1583  if ( sample_distribution[d] == 0 ) /* Uninteresting dimensions don't get any room */
1584  {
1585  histo_size[d] = 1;
1586  }
1587  else /* Interesting dimension */
1588  {
1589  /* How does this dims variability compare to the total? */
1590  float edge_ratio = (float)sample_distribution[d] / (float)total_distribution;
1591  /*
1592  * Scale the target cells number by the # of dims and ratio,
1593  * then take the appropriate root to get the estimated number of cells
1594  * on this axis (eg, pow(0.5) for 2d, pow(0.333) for 3d, pow(0.25) for 4d)
1595  */
1596  histo_size[d] = (int)pow(histo_cells_target * histo_ndims * edge_ratio, 1/(double)histo_ndims);
1597  /* If something goes awry, just give this dim one slot */
1598  if ( ! histo_size[d] )
1599  histo_size[d] = 1;
1600  }
1601  histo_cells_new *= histo_size[d];
1602  }
1603  POSTGIS_DEBUGF(3, " histo_cells_new: %d", histo_cells_new);
1604  }
1605 
1606  /* Update histo_cells to the actual number of cells we need to allocate */
1607  histo_cells = histo_cells_new;
1608  POSTGIS_DEBUGF(3, " histo_cells: %d", histo_cells);
1609 
1610  /*
1611  * Create the histogram (ND_STATS) in the stats memory context
1612  */
1613  old_context = MemoryContextSwitchTo(stats->anl_context);
1614  nd_stats_size = sizeof(ND_STATS) + ((histo_cells - 1) * sizeof(float4));
1615  nd_stats = palloc(nd_stats_size);
1616  memset(nd_stats, 0, nd_stats_size); /* Initialize all values to 0 */
1617  MemoryContextSwitchTo(old_context);
1618 
1619  /* Initialize the #ND_STATS objects */
1620  nd_stats->ndims = ndims;
1621  nd_stats->extent = histo_extent;
1622  nd_stats->sample_features = sample_rows;
1623  nd_stats->table_features = total_rows;
1624  nd_stats->not_null_features = notnull_cnt;
1625  /* Copy in the histogram dimensions */
1626  for ( d = 0; d < ndims; d++ )
1627  nd_stats->size[d] = histo_size[d];
1628 
1629  /*
1630  * Fourth scan:
1631  * o fill histogram values with the proportion of
1632  * features' bbox overlaps: a feature's bvol
1633  * can fully overlap (1) or partially overlap
1634  * (fraction of 1) an histogram cell.
1635  *
1636  * Note that we are filling each cell with the "portion of
1637  * the feature's box that overlaps the cell". So, if we sum
1638  * up the values in the histogram, we could get the
1639  * histogram feature count.
1640  *
1641  */
1642  for ( i = 0; i < notnull_cnt; i++ )
1643  {
1644  const ND_BOX *nd_box;
1645  ND_IBOX nd_ibox;
1646  int at[ND_DIMS];
1647  int d;
1648  double num_cells = 0;
1649  double tmp_volume = 1.0;
1650  double min[ND_DIMS];
1651  double max[ND_DIMS];
1652  double cellsize[ND_DIMS];
1653 
1654  nd_box = sample_boxes[i];
1655  if ( ! nd_box ) continue; /* Skip Null'ed out hard deviants */
1656 
1657  /* Give backend a chance of interrupting us */
1658  vacuum_delay_point();
1659 
1660  /* Find the cells that overlap with this box and put them into the ND_IBOX */
1661  nd_box_overlap(nd_stats, nd_box, &nd_ibox);
1662  memset(at, 0, sizeof(int)*ND_DIMS);
1663 
1664  POSTGIS_DEBUGF(3, " feature %d: ibox (%d, %d, %d, %d) (%d, %d, %d, %d)", i,
1665  nd_ibox.min[0], nd_ibox.min[1], nd_ibox.min[2], nd_ibox.min[3],
1666  nd_ibox.max[0], nd_ibox.max[1], nd_ibox.max[2], nd_ibox.max[3]);
1667 
1668  for ( d = 0; d < nd_stats->ndims; d++ )
1669  {
1670  /* Initialize the starting values */
1671  at[d] = nd_ibox.min[d];
1672  min[d] = nd_stats->extent.min[d];
1673  max[d] = nd_stats->extent.max[d];
1674  cellsize[d] = (max[d] - min[d])/(nd_stats->size[d]);
1675 
1676  /* What's the volume (area) of this feature's box? */
1677  tmp_volume *= (nd_box->max[d] - nd_box->min[d]);
1678  }
1679 
1680  /* Add feature volume (area) to our total */
1681  total_sample_volume += tmp_volume;
1682 
1683  /*
1684  * Move through all the overlaped histogram cells values and
1685  * add the box overlap proportion to them.
1686  */
1687  do
1688  {
1689  ND_BOX nd_cell;
1690  double ratio;
1691  /* Create a box for this histogram cell */
1692  for ( d = 0; d < nd_stats->ndims; d++ )
1693  {
1694  nd_cell.min[d] = min[d] + (at[d]+0) * cellsize[d];
1695  nd_cell.max[d] = min[d] + (at[d]+1) * cellsize[d];
1696  }
1697 
1698  /*
1699  * If a feature box is completely inside one cell the ratio will be
1700  * 1.0. If a feature box is 50% in two cells, each cell will get
1701  * 0.5 added on.
1702  */
1703  ratio = nd_box_ratio(&nd_cell, nd_box, nd_stats->ndims);
1704  nd_stats->value[nd_stats_value_index(nd_stats, at)] += ratio;
1705  num_cells += ratio;
1706  POSTGIS_DEBUGF(3, " ratio (%.8g) num_cells (%.8g)", ratio, num_cells);
1707  POSTGIS_DEBUGF(3, " at (%d, %d, %d, %d)", at[0], at[1], at[2], at[3]);
1708  }
1709  while ( nd_increment(&nd_ibox, nd_stats->ndims, at) );
1710 
1711  /* Keep track of overall number of overlaps counted */
1712  total_cell_count += num_cells;
1713  /* How many features have we added to this histogram? */
1714  histogram_features++;
1715  }
1716 
1717  POSTGIS_DEBUGF(3, " histogram_features: %d", histogram_features);
1718  POSTGIS_DEBUGF(3, " sample_rows: %d", sample_rows);
1719  POSTGIS_DEBUGF(3, " table_rows: %.6g", total_rows);
1720 
1721  /* Error out if we got no sample information */
1722  if ( ! histogram_features )
1723  {
1724  POSTGIS_DEBUG(3, " no stats have been gathered");
1725  elog(NOTICE, " no features lie in the stats histogram, invalid stats");
1726  stats->stats_valid = false;
1727  return;
1728  }
1729 
1730  nd_stats->histogram_features = histogram_features;
1731  nd_stats->histogram_cells = histo_cells;
1732  nd_stats->cells_covered = total_cell_count;
1733 
1734  /* Put this histogram data into the right slot/kind */
1735  if ( mode == 2 )
1736  {
1737  stats_slot = STATISTIC_SLOT_2D;
1738  stats_kind = STATISTIC_KIND_2D;
1739  }
1740  else
1741  {
1742  stats_slot = STATISTIC_SLOT_ND;
1743  stats_kind = STATISTIC_KIND_ND;
1744  }
1745 
1746  /* Write the statistics data */
1747  stats->stakind[stats_slot] = stats_kind;
1748  stats->staop[stats_slot] = InvalidOid;
1749  stats->stanumbers[stats_slot] = (float4*)nd_stats;
1750  stats->numnumbers[stats_slot] = nd_stats_size/sizeof(float4);
1751  stats->stanullfrac = (float4)null_cnt/sample_rows;
1752  stats->stawidth = total_width/notnull_cnt;
1753  stats->stadistinct = -1.0;
1754  stats->stats_valid = true;
1755 
1756  POSTGIS_DEBUGF(3, " out: slot 0: kind %d (STATISTIC_KIND_ND)", stats->stakind[0]);
1757  POSTGIS_DEBUGF(3, " out: slot 0: op %d (InvalidOid)", stats->staop[0]);
1758  POSTGIS_DEBUGF(3, " out: slot 0: numnumbers %d", stats->numnumbers[0]);
1759  POSTGIS_DEBUGF(3, " out: null fraction: %f=%d/%d", stats->stanullfrac, null_cnt, sample_rows);
1760  POSTGIS_DEBUGF(3, " out: average width: %d bytes", stats->stawidth);
1761  POSTGIS_DEBUG (3, " out: distinct values: all (no check done)");
1762  POSTGIS_DEBUGF(3, " out: %s", nd_stats_to_json(nd_stats));
1763  /*
1764  POSTGIS_DEBUGF(3, " out histogram:\n%s", nd_stats_to_grid(nd_stats));
1765  */
1766 
1767  return;
1768 }
int gserialized_get_gbox_p(const GSERIALIZED *g, GBOX *box)
Read the bounding box off a serialization and calculate one if it is not already there.
Definition: g_serialized.c:398
static int nd_increment(ND_IBOX *ibox, int ndims, int *counter)
Given an n-d index array (counter), and a domain to increment it in (ibox) increment it by one...
static int nd_box_overlap(const ND_STATS *nd_stats, const ND_BOX *nd_box, ND_IBOX *nd_ibox)
What stats cells overlap with this ND_BOX? Put the lowest cell addresses in ND_IBOX->min and the high...
int gbox_is_valid(const GBOX *gbox)
Return false if any of the dimensions is NaN or infinite.
Definition: g_box.c:209
#define ND_DIMS
The maximum number of dimensions our code can handle.
#define STATISTIC_SLOT_2D
static void nd_box_from_gbox(const GBOX *gbox, ND_BOX *nd_box)
Set the values of an ND_BOX from a GBOX.
#define LW_FAILURE
Definition: liblwgeom.h:78
static int nd_box_init(ND_BOX *a)
Zero out an ND_BOX.
double zmax
Definition: liblwgeom.h:296
static int nd_box_array_distribution(const ND_BOX **nd_boxes, int num_boxes, const ND_BOX *extent, int ndims, double *distribution)
Calculate how much a set of boxes is homogenously distributed or contentrated within one dimension...
static int nd_stats_value_index(const ND_STATS *stats, int *indexes)
Given a position in the n-d histogram (i,j,k) return the position in the 1-d values array...
float4 size[ND_DIMS]
static int nd_box_intersects(const ND_BOX *a, const ND_BOX *b, int ndims)
Return TRUE if ND_BOX a overlaps b, false otherwise.
static double total_double(const double *vals, int nvals)
Given double array, return sum of values.
int min[ND_DIMS]
static int nd_box_init_bounds(ND_BOX *a)
Prepare an ND_BOX for bounds calculation: set the maxes to the smallest thing possible and the mins t...
N-dimensional box index type.
static char * nd_box_to_json(const ND_BOX *nd_box, int ndims)
Convert an ND_BOX to a JSON string for printing.
float4 max[ND_DIMS]
#define STATISTIC_KIND_2D
int max[ND_DIMS]
struct ND_STATS_T ND_STATS
N-dimensional statistics structure.
float4 min[ND_DIMS]
static int nd_box_merge(const ND_BOX *source, ND_BOX *target)
Create a printable view of the ND_STATS histogram.
static int nd_box_expand(ND_BOX *nd_box, double expansion_factor)
Expand an ND_BOX ever so slightly.
double mmin
Definition: liblwgeom.h:297
#define SDFACTOR
double zmin
Definition: liblwgeom.h:295
static char * nd_stats_to_json(const ND_STATS *nd_stats)
Convert an ND_STATS to a JSON representation for external use.
double mmax
Definition: liblwgeom.h:298
#define STATISTIC_SLOT_ND
static double nd_box_ratio(const ND_BOX *b1, const ND_BOX *b2, int ndims)
Returns the proportion of b2 that is covered by b1.
N-dimensional statistics structure.
N-dimensional box type for calculations, to avoid doing explicit axis conversions from GBOX in all ca...
static int gbox_ndims(const GBOX *gbox)
Given that geodetic boxes are X/Y/Z regardless of the underlying geometry dimensionality and other bo...
#define STATISTIC_KIND_ND
Assign a number to the n-dimensional statistics kind.
Here is the call graph for this function:
Here is the caller graph for this function: