PostGIS  2.1.10dev-r@@SVN_REVISION@@
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 1215 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().

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