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 1301 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().

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