PostGIS  2.5.0dev-r@@SVN_REVISION@@
int lwt_ChangeEdgeGeom ( LWT_TOPOLOGY topo,
LWT_ELEMID  edge,
LWLINE curve 
)

Changes the shape of an edge without affecting the topology structure.

For ST_ChangeEdgeGeom

Parameters
topothe topology to operate on
curvethe edge geometry
Returns
0 on success, -1 on error (liblwgeom error handler will be invoked with error message)

Definition at line 3331 of file lwgeom_topo.c.

References _lwt_CheckEdgeCrossing(), _lwt_EdgeMotionArea(), _lwt_FindAdjacentEdges(), _lwt_GetInteriorEdgePoint(), _lwt_InitEdgeEndByLine(), _lwt_release_edges(), _lwt_release_nodes(), LWGEOM::bbox, LWLINE::bbox, LWT_TOPOLOGY_T::be_iface, LWT_ISO_EDGE::edge_id, LWT_ISO_EDGE::end_node, LWT_ISO_FACE::face_id, LWT_ISO_EDGE::face_left, LWT_ISO_EDGE::face_right, gbox_same(), gbox_union(), LWT_ISO_NODE::geom, LWT_ISO_EDGE::geom, getPoint2d_p(), LWDEBUG, LWDEBUGF, lwerror(), lwfree(), LWGEOM2GEOS(), lwgeom_add_bbox(), lwgeom_free(), lwgeom_geos_errmsg, lwgeom_geos_error(), lwgeom_get_bbox(), lwgeom_is_simple(), lwgeom_to_wkt(), lwline_as_lwgeom(), lwnotice(), lwpoint_as_lwgeom(), lwt_be_getEdgeById(), lwt_be_getNodeWithinBox2D(), lwt_be_lastErrorMessage(), lwt_be_updateEdgesById(), lwt_be_updateFacesById(), LWT_COL_EDGE_ALL, LWT_COL_EDGE_GEOM, LWT_COL_NODE_ALL, lwt_GetFaceGeometry(), LWTFMT_ELEMID, LWT_ISO_FACE::mbr, edgeend_t::nextCCW, edgeend_t::nextCW, LWT_ISO_NODE::node_id, POINTARRAY::npoints, p2d_same(), LWLINE::points, ptarray_isccw(), LWT_ISO_EDGE::start_node, WKT_EXTENDED, WKT_ISO, POINT2D::x, and POINT2D::y.

Referenced by _lwt_AddPoint().

3332 {
3333  LWT_ISO_EDGE *oldedge;
3334  LWT_ISO_EDGE newedge;
3335  POINT2D p1, p2, pt;
3336  int i;
3337  int isclosed = 0;
3338 
3339  /* curve must be simple */
3340  if ( ! lwgeom_is_simple(lwline_as_lwgeom(geom)) )
3341  {
3342  lwerror("SQL/MM Spatial exception - curve not simple");
3343  return -1;
3344  }
3345 
3346  i = 1;
3347  oldedge = lwt_be_getEdgeById(topo, &edge_id, &i, LWT_COL_EDGE_ALL);
3348  if ( ! oldedge )
3349  {
3350  LWDEBUGF(1, "lwt_ChangeEdgeGeom: "
3351  "lwt_be_getEdgeById returned NULL and set i=%d", i);
3352  if ( i == -1 )
3353  {
3354  lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
3355  return -1;
3356  }
3357  else if ( i == 0 )
3358  {
3359  lwerror("SQL/MM Spatial exception - non-existent edge %"
3360  LWTFMT_ELEMID, edge_id);
3361  return -1;
3362  }
3363  else
3364  {
3365  lwerror("Backend coding error: getEdgeById callback returned NULL "
3366  "but numelements output parameter has value %d "
3367  "(expected 0 or 1)", i);
3368  return -1;
3369  }
3370  }
3371 
3372  LWDEBUGF(1, "lwt_ChangeEdgeGeom: "
3373  "old edge has %d points, new edge has %d points",
3374  oldedge->geom->points->npoints, geom->points->npoints);
3375 
3376  /*
3377  * e) Check StartPoint consistency
3378  */
3379  getPoint2d_p(oldedge->geom->points, 0, &p1);
3380  getPoint2d_p(geom->points, 0, &pt);
3381  if ( ! p2d_same(&p1, &pt) )
3382  {
3383  _lwt_release_edges(oldedge, 1);
3384  lwerror("SQL/MM Spatial exception - "
3385  "start node not geometry start point.");
3386  return -1;
3387  }
3388 
3389  /*
3390  * f) Check EndPoint consistency
3391  */
3392  if ( oldedge->geom->points->npoints < 2 )
3393  {
3394  _lwt_release_edges(oldedge, 1);
3395  lwerror("Corrupted topology: edge %" LWTFMT_ELEMID
3396  " has less than 2 vertices", oldedge->edge_id);
3397  return -1;
3398  }
3399  getPoint2d_p(oldedge->geom->points, oldedge->geom->points->npoints-1, &p2);
3400  if ( geom->points->npoints < 2 )
3401  {
3402  _lwt_release_edges(oldedge, 1);
3403  lwerror("Invalid edge: less than 2 vertices");
3404  return -1;
3405  }
3406  getPoint2d_p(geom->points, geom->points->npoints-1, &pt);
3407  if ( ! p2d_same(&pt, &p2) )
3408  {
3409  _lwt_release_edges(oldedge, 1);
3410  lwerror("SQL/MM Spatial exception - "
3411  "end node not geometry end point.");
3412  return -1;
3413  }
3414 
3415  /* Not in the specs:
3416  * if the edge is closed, check we didn't change winding !
3417  * (should be part of isomorphism checking)
3418  */
3419  if ( oldedge->start_node == oldedge->end_node )
3420  {
3421  isclosed = 1;
3422 #if 1 /* TODO: this is actually bogus as a test */
3423  /* check for valid edge (distinct vertices must exist) */
3424  if ( ! _lwt_GetInteriorEdgePoint(geom, &pt) )
3425  {
3426  _lwt_release_edges(oldedge, 1);
3427  lwerror("Invalid edge (no two distinct vertices exist)");
3428  return -1;
3429  }
3430 #endif
3431 
3432  if ( ptarray_isccw(oldedge->geom->points) !=
3433  ptarray_isccw(geom->points) )
3434  {
3435  _lwt_release_edges(oldedge, 1);
3436  lwerror("Edge twist at node POINT(%g %g)", p1.x, p1.y);
3437  return -1;
3438  }
3439  }
3440 
3441  if ( _lwt_CheckEdgeCrossing(topo, oldedge->start_node,
3442  oldedge->end_node, geom, edge_id ) )
3443  {
3444  /* would have called lwerror already, leaking :( */
3445  _lwt_release_edges(oldedge, 1);
3446  return -1;
3447  }
3448 
3449  LWDEBUG(1, "lwt_ChangeEdgeGeom: "
3450  "edge crossing check passed ");
3451 
3452  /*
3453  * Not in the specs:
3454  * Check topological isomorphism
3455  */
3456 
3457  /* Check that the "motion range" doesn't include any node */
3458  // 1. compute combined bbox of old and new edge
3459  GBOX mbox; /* motion box */
3460  lwgeom_add_bbox((LWGEOM*)oldedge->geom); /* just in case */
3461  lwgeom_add_bbox((LWGEOM*)geom); /* just in case */
3462  gbox_union(oldedge->geom->bbox, geom->bbox, &mbox);
3463  // 2. fetch all nodes in the combined box
3464  LWT_ISO_NODE *nodes;
3465  int numnodes;
3466  nodes = lwt_be_getNodeWithinBox2D(topo, &mbox, &numnodes,
3467  LWT_COL_NODE_ALL, 0);
3468  LWDEBUGF(1, "lwt_be_getNodeWithinBox2D returned %d nodes", numnodes);
3469  if ( numnodes == -1 ) {
3470  _lwt_release_edges(oldedge, 1);
3471  lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
3472  return -1;
3473  }
3474  // 3. if any node beside endnodes are found:
3475  if ( numnodes > ( 1 + isclosed ? 0 : 1 ) )
3476  {{
3477  GEOSGeometry *oarea, *narea;
3478  const GEOSPreparedGeometry *oareap, *nareap;
3479 
3480  initGEOS(lwnotice, lwgeom_geos_error);
3481 
3482  oarea = _lwt_EdgeMotionArea(oldedge->geom, isclosed);
3483  if ( ! oarea )
3484  {
3485  _lwt_release_edges(oldedge, 1);
3486  lwerror("Could not compute edge motion area for old edge");
3487  return -1;
3488  }
3489 
3490  narea = _lwt_EdgeMotionArea(geom, isclosed);
3491  if ( ! narea )
3492  {
3493  GEOSGeom_destroy(oarea);
3494  _lwt_release_edges(oldedge, 1);
3495  lwerror("Could not compute edge motion area for new edge");
3496  return -1;
3497  }
3498 
3499  // 3.2. bail out if any node is in one and not the other
3500  oareap = GEOSPrepare( oarea );
3501  nareap = GEOSPrepare( narea );
3502  for (i=0; i<numnodes; ++i)
3503  {
3504  LWT_ISO_NODE *n = &(nodes[i]);
3505  GEOSGeometry *ngg;
3506  int ocont, ncont;
3507  size_t sz;
3508  char *wkt;
3509  if ( n->node_id == oldedge->start_node ) continue;
3510  if ( n->node_id == oldedge->end_node ) continue;
3511  ngg = LWGEOM2GEOS( lwpoint_as_lwgeom(n->geom) , 0);
3512  ocont = GEOSPreparedContains( oareap, ngg );
3513  ncont = GEOSPreparedContains( nareap, ngg );
3514  GEOSGeom_destroy(ngg);
3515  if (ocont == 2 || ncont == 2)
3516  {
3517  _lwt_release_nodes(nodes, numnodes);
3518  GEOSPreparedGeom_destroy(oareap);
3519  GEOSGeom_destroy(oarea);
3520  GEOSPreparedGeom_destroy(nareap);
3521  GEOSGeom_destroy(narea);
3522  lwerror("GEOS exception on PreparedContains: %s", lwgeom_geos_errmsg);
3523  return -1;
3524  }
3525  if (ocont != ncont)
3526  {
3527  GEOSPreparedGeom_destroy(oareap);
3528  GEOSGeom_destroy(oarea);
3529  GEOSPreparedGeom_destroy(nareap);
3530  GEOSGeom_destroy(narea);
3531  wkt = lwgeom_to_wkt(lwpoint_as_lwgeom(n->geom), WKT_ISO, 15, &sz);
3532  _lwt_release_nodes(nodes, numnodes);
3533  lwerror("Edge motion collision at %s", wkt);
3534  lwfree(wkt); /* would not necessarely reach this point */
3535  return -1;
3536  }
3537  }
3538  GEOSPreparedGeom_destroy(oareap);
3539  GEOSGeom_destroy(oarea);
3540  GEOSPreparedGeom_destroy(nareap);
3541  GEOSGeom_destroy(narea);
3542  }}
3543  if ( numnodes ) _lwt_release_nodes(nodes, numnodes);
3544 
3545  LWDEBUG(1, "nodes containment check passed");
3546 
3547  /*
3548  * Check edge adjacency before
3549  * TODO: can be optimized to gather azimuths of all edge ends once
3550  */
3551 
3552  edgeend span_pre, epan_pre;
3553  /* initialize span_pre.myaz and epan_pre.myaz with existing edge */
3554  i = _lwt_InitEdgeEndByLine(&span_pre, &epan_pre,
3555  oldedge->geom, &p1, &p2);
3556  if ( i ) return -1; /* lwerror should have been raised */
3557  _lwt_FindAdjacentEdges( topo, oldedge->start_node, &span_pre,
3558  isclosed ? &epan_pre : NULL, edge_id );
3559  _lwt_FindAdjacentEdges( topo, oldedge->end_node, &epan_pre,
3560  isclosed ? &span_pre : NULL, edge_id );
3561 
3562  LWDEBUGF(1, "edges adjacent to old edge are %" LWTFMT_ELEMID
3563  " and %" LWTFMT_ELEMID " (first point), %" LWTFMT_ELEMID
3564  " and %" LWTFMT_ELEMID " (last point)",
3565  span_pre.nextCW, span_pre.nextCCW,
3566  epan_pre.nextCW, epan_pre.nextCCW);
3567 
3568  /* update edge geometry */
3569  newedge.edge_id = edge_id;
3570  newedge.geom = geom;
3571  i = lwt_be_updateEdgesById(topo, &newedge, 1, LWT_COL_EDGE_GEOM);
3572  if ( i == -1 )
3573  {
3574  _lwt_release_edges(oldedge, 1);
3575  lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
3576  return -1;
3577  }
3578  if ( ! i )
3579  {
3580  _lwt_release_edges(oldedge, 1);
3581  lwerror("Unexpected error: %d edges updated when expecting 1", i);
3582  return -1;
3583  }
3584 
3585  /*
3586  * Check edge adjacency after
3587  */
3588  edgeend span_post, epan_post;
3589  i = _lwt_InitEdgeEndByLine(&span_post, &epan_post, geom, &p1, &p2);
3590  if ( i ) return -1; /* lwerror should have been raised */
3591  /* initialize epan_post.myaz and epan_post.myaz */
3592  i = _lwt_InitEdgeEndByLine(&span_post, &epan_post,
3593  geom, &p1, &p2);
3594  if ( i ) return -1; /* lwerror should have been raised */
3595  _lwt_FindAdjacentEdges( topo, oldedge->start_node, &span_post,
3596  isclosed ? &epan_post : NULL, edge_id );
3597  _lwt_FindAdjacentEdges( topo, oldedge->end_node, &epan_post,
3598  isclosed ? &span_post : NULL, edge_id );
3599 
3600  LWDEBUGF(1, "edges adjacent to new edge are %" LWTFMT_ELEMID
3601  " and %" LWTFMT_ELEMID " (first point), %" LWTFMT_ELEMID
3602  " and %" LWTFMT_ELEMID " (last point)",
3603  span_pre.nextCW, span_pre.nextCCW,
3604  epan_pre.nextCW, epan_pre.nextCCW);
3605 
3606 
3607  /* Bail out if next CW or CCW edge on start node changed */
3608  if ( span_pre.nextCW != span_post.nextCW ||
3609  span_pre.nextCCW != span_post.nextCCW )
3610  {{
3611  LWT_ELEMID nid = oldedge->start_node;
3612  _lwt_release_edges(oldedge, 1);
3613  lwerror("Edge changed disposition around start node %"
3614  LWTFMT_ELEMID, nid);
3615  return -1;
3616  }}
3617 
3618  /* Bail out if next CW or CCW edge on end node changed */
3619  if ( epan_pre.nextCW != epan_post.nextCW ||
3620  epan_pre.nextCCW != epan_post.nextCCW )
3621  {{
3622  LWT_ELEMID nid = oldedge->end_node;
3623  _lwt_release_edges(oldedge, 1);
3624  lwerror("Edge changed disposition around end node %"
3625  LWTFMT_ELEMID, nid);
3626  return -1;
3627  }}
3628 
3629  /*
3630  -- Update faces MBR of left and right faces
3631  -- TODO: think about ways to optimize this part, like see if
3632  -- the old edge geometry partecipated in the definition
3633  -- of the current MBR (for shrinking) or the new edge MBR
3634  -- would be larger than the old face MBR...
3635  --
3636  */
3637  const GBOX* oldbox = lwgeom_get_bbox(lwline_as_lwgeom(oldedge->geom));
3638  const GBOX* newbox = lwgeom_get_bbox(lwline_as_lwgeom(geom));
3639  if ( ! gbox_same(oldbox, newbox) )
3640  {{
3641  int facestoupdate = 0;
3642  LWT_ISO_FACE faces[2];
3643  LWGEOM *nface1 = NULL;
3644  LWGEOM *nface2 = NULL;
3645  if ( oldedge->face_left > 0 )
3646  {
3647  nface1 = lwt_GetFaceGeometry(topo, oldedge->face_left);
3648  if ( ! nface1 )
3649  {
3650  lwerror("lwt_ChangeEdgeGeom could not construct face %"
3651  LWTFMT_ELEMID ", on the left of edge %" LWTFMT_ELEMID,
3652  oldedge->face_left, edge_id);
3653  return -1;
3654  }
3655  #if 0
3656  {
3657  size_t sz;
3658  char *wkt = lwgeom_to_wkt(nface1, WKT_EXTENDED, 2, &sz);
3659  LWDEBUGF(1, "new geometry of face left (%d): %s", (int)oldedge->face_left, wkt);
3660  lwfree(wkt);
3661  }
3662  #endif
3663  lwgeom_add_bbox(nface1);
3664  faces[facestoupdate].face_id = oldedge->face_left;
3665  /* ownership left to nface */
3666  faces[facestoupdate++].mbr = nface1->bbox;
3667  }
3668  if ( oldedge->face_right > 0
3669  /* no need to update twice the same face.. */
3670  && oldedge->face_right != oldedge->face_left )
3671  {
3672  nface2 = lwt_GetFaceGeometry(topo, oldedge->face_right);
3673  if ( ! nface2 )
3674  {
3675  lwerror("lwt_ChangeEdgeGeom could not construct face %"
3676  LWTFMT_ELEMID ", on the right of edge %" LWTFMT_ELEMID,
3677  oldedge->face_right, edge_id);
3678  return -1;
3679  }
3680  #if 0
3681  {
3682  size_t sz;
3683  char *wkt = lwgeom_to_wkt(nface2, WKT_EXTENDED, 2, &sz);
3684  LWDEBUGF(1, "new geometry of face right (%d): %s", (int)oldedge->face_right, wkt);
3685  lwfree(wkt);
3686  }
3687  #endif
3688  lwgeom_add_bbox(nface2);
3689  faces[facestoupdate].face_id = oldedge->face_right;
3690  faces[facestoupdate++].mbr = nface2->bbox; /* ownership left to nface */
3691  }
3692  LWDEBUGF(1, "%d faces to update", facestoupdate);
3693  if ( facestoupdate )
3694  {
3695  i = lwt_be_updateFacesById( topo, &(faces[0]), facestoupdate );
3696  if ( i != facestoupdate )
3697  {
3698  if ( nface1 ) lwgeom_free(nface1);
3699  if ( nface2 ) lwgeom_free(nface2);
3700  _lwt_release_edges(oldedge, 1);
3701  if ( i == -1 )
3702  lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
3703  else
3704  lwerror("Unexpected error: %d faces found when expecting 1", i);
3705  return -1;
3706  }
3707  }
3708  if ( nface1 ) lwgeom_free(nface1);
3709  if ( nface2 ) lwgeom_free(nface2);
3710  }} else {{
3711  lwnotice("BBOX of changed edge did not change");
3712  }}
3713 
3714  LWDEBUG(1, "all done, cleaning up edges");
3715 
3716  _lwt_release_edges(oldedge, 1);
3717  return 0; /* success */
3718 }
static int _lwt_CheckEdgeCrossing(LWT_TOPOLOGY *topo, LWT_ELEMID start_node, LWT_ELEMID end_node, const LWLINE *geom, LWT_ELEMID myself)
Definition: lwgeom_topo.c:601
static void _lwt_release_nodes(LWT_ISO_NODE *nodes, int num_nodes)
Definition: lwgeom_topo.c:467
static int _lwt_GetInteriorEdgePoint(const LWLINE *edge, POINT2D *ip)
Definition: lwgeom_topo.c:1737
LWT_ELEMID face_left
GBOX * bbox
Definition: liblwgeom.h:397
GBOX * bbox
Definition: liblwgeom.h:419
static int lwt_be_updateEdgesById(LWT_TOPOLOGY *topo, const LWT_ISO_EDGE *edges, int numedges, int upd_fields)
Definition: lwgeom_topo.c:306
LWPOINT * geom
int ptarray_isccw(const POINTARRAY *pa)
Definition: ptarray.c:1023
char * lwgeom_to_wkt(const LWGEOM *geom, uint8_t variant, int precision, size_t *size_out)
WKT emitter function.
Definition: lwout_wkt.c:675
void lwnotice(const char *fmt,...)
Write a notice out to the notice handler.
Definition: lwutil.c:177
void lwfree(void *mem)
Definition: lwutil.c:244
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1137
char lwgeom_geos_errmsg[LWGEOM_GEOS_ERRMSG_MAXSIZE]
LWLINE * geom
#define LWDEBUG(level, msg)
Definition: lwgeom_log.h:83
LWT_ISO_EDGE * lwt_be_getEdgeById(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, int *numelems, int fields)
Definition: lwgeom_topo.c:225
LWT_ELEMID nextCCW
Definition: lwgeom_topo.c:1405
double x
Definition: liblwgeom.h:327
int p2d_same(const POINT2D *p1, const POINT2D *p2)
Definition: lwalgorithm.c:49
#define WKT_ISO
Definition: liblwgeom.h:2068
static int lwt_be_updateFacesById(LWT_TOPOLOGY *topo, const LWT_ISO_FACE *faces, int numfaces)
Definition: lwgeom_topo.c:298
LWGEOM * lwline_as_lwgeom(const LWLINE *obj)
Definition: lwgeom.c:329
void lwgeom_geos_error(const char *fmt,...)
const LWT_BE_IFACE * be_iface
LWT_ELEMID face_id
const GBOX * lwgeom_get_bbox(const LWGEOM *lwgeom)
Get a non-empty geometry bounding box, computing and caching it if not already there.
Definition: lwgeom.c:727
LWT_ELEMID face_right
static GEOSGeometry * _lwt_EdgeMotionArea(LWLINE *geom, int isclosed)
Definition: lwgeom_topo.c:3275
#define LWT_COL_EDGE_ALL
LWT_ELEMID node_id
double y
Definition: liblwgeom.h:327
LWT_ELEMID edge_id
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, int autofix)
int gbox_union(const GBOX *g1, const GBOX *g2, GBOX *gout)
Update the output GBOX to be large enough to include both inputs.
Definition: g_box.c:146
int getPoint2d_p(const POINTARRAY *pa, uint32_t n, POINT2D *point)
Definition: lwgeom_api.c:338
#define WKT_EXTENDED
Definition: liblwgeom.h:2070
static int _lwt_FindAdjacentEdges(LWT_TOPOLOGY *topo, LWT_ELEMID node, edgeend *data, edgeend *other, int myedge_id)
Definition: lwgeom_topo.c:1519
LWGEOM * lwt_GetFaceGeometry(LWT_TOPOLOGY *topo, LWT_ELEMID faceid)
Return the geometry of a face.
Definition: lwgeom_topo.c:2882
#define LWT_COL_NODE_ALL
LWT_ELEMID start_node
int lwgeom_is_simple(const LWGEOM *lwgeom)
static void _lwt_release_edges(LWT_ISO_EDGE *edges, int num_edges)
Definition: lwgeom_topo.c:457
void lwgeom_add_bbox(LWGEOM *lwgeom)
Compute a bbox if not already computed.
Definition: lwgeom.c:679
LWGEOM * lwpoint_as_lwgeom(const LWPOINT *obj)
Definition: lwgeom.c:334
LWT_INT64 LWT_ELEMID
Identifier of topology element.
#define LWT_COL_EDGE_GEOM
static int _lwt_InitEdgeEndByLine(edgeend *fee, edgeend *lee, LWLINE *edge, POINT2D *fp, POINT2D *lp)
Definition: lwgeom_topo.c:1461
LWT_ELEMID nextCW
Definition: lwgeom_topo.c:1401
LWT_ELEMID end_node
#define LWDEBUGF(level, msg,...)
Definition: lwgeom_log.h:88
int gbox_same(const GBOX *g1, const GBOX *g2)
Check if 2 given Gbox are the same.
Definition: g_box.c:176
static LWT_ISO_NODE * lwt_be_getNodeWithinBox2D(const LWT_TOPOLOGY *topo, const GBOX *box, int *numelems, int fields, int limit)
Definition: lwgeom_topo.c:171
const char * lwt_be_lastErrorMessage(const LWT_BE_IFACE *be)
Definition: lwgeom_topo.c:120
void lwerror(const char *fmt,...)
Write a notice out to the error handler.
Definition: lwutil.c:190
#define LWTFMT_ELEMID
Definition: lwgeom_topo.c:44
POINTARRAY * points
Definition: liblwgeom.h:421
uint32_t npoints
Definition: liblwgeom.h:370

Here is the call graph for this function:

Here is the caller graph for this function: