PostGIS 3.0.6dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches

◆ _lwt_AddEdge()

static LWT_ELEMID _lwt_AddEdge ( LWT_TOPOLOGY topo,
LWT_ELEMID  start_node,
LWT_ELEMID  end_node,
LWLINE geom,
int  skipChecks,
int  modFace 
)
static
Parameters
modFacecan be 0 - have two new faces replace a splitted face 1 - modify a splitted face, adding a new one -1 - do not check at all for face splitting

Definition at line 2267 of file lwgeom_topo.c.

2270{
2271 LWT_ISO_EDGE newedge;
2272 LWGEOM *cleangeom;
2273 edgeend span; /* start point analisys */
2274 edgeend epan; /* end point analisys */
2275 POINT2D p1, pn, p2;
2276 POINTARRAY *pa;
2277 LWT_ELEMID node_ids[2];
2278 const LWPOINT *start_node_geom = NULL;
2279 const LWPOINT *end_node_geom = NULL;
2280 uint64_t num_nodes;
2281 LWT_ISO_NODE *endpoints;
2282 uint64_t i;
2283 int prev_left;
2284 int prev_right;
2285 LWT_ISO_EDGE seledge;
2286 LWT_ISO_EDGE updedge;
2287
2288 if ( ! skipChecks )
2289 {
2290 /* curve must be simple */
2291 if ( ! lwgeom_is_simple(lwline_as_lwgeom(geom)) )
2292 {
2293 lwerror("SQL/MM Spatial exception - curve not simple");
2294 return -1;
2295 }
2296 }
2297
2298 newedge.start_node = start_node;
2299 newedge.end_node = end_node;
2300 newedge.geom = geom;
2301 newedge.face_left = -1;
2302 newedge.face_right = -1;
2303 /* TODO: should do the repeated points removal in 2D space */
2304 cleangeom = lwgeom_remove_repeated_points( lwline_as_lwgeom(geom), 0 );
2305
2306 pa = lwgeom_as_lwline(cleangeom)->points;
2307 if ( pa->npoints < 2 ) {
2308 lwgeom_free(cleangeom);
2309 lwerror("Invalid edge (no two distinct vertices exist)");
2310 return -1;
2311 }
2312
2313 /* Initialize endpoint info (some of that ) */
2314 span.cwFace = span.ccwFace =
2315 epan.cwFace = epan.ccwFace = -1;
2316
2317 /* Compute azimuth of first edge end on start node */
2318 getPoint2d_p(pa, 0, &p1);
2319 if ( ! _lwt_FirstDistinctVertex2D(pa, &p1, 0, 1, &pn) )
2320 {
2321 lwgeom_free(cleangeom);
2322 lwerror("Invalid edge (no two distinct vertices exist)");
2323 return -1;
2324 }
2325 if ( ! azimuth_pt_pt(&p1, &pn, &span.myaz) ) {
2326 lwgeom_free(cleangeom);
2327 lwerror("error computing azimuth of first edgeend [%.15g %.15g,%.15g %.15g]",
2328 p1.x, p1.y, pn.x, pn.y);
2329 return -1;
2330 }
2331 LWDEBUGF(1, "edge's start node is %g,%g", p1.x, p1.y);
2332
2333 /* Compute azimuth of last edge end on end node */
2334 getPoint2d_p(pa, pa->npoints-1, &p2);
2335 if ( ! _lwt_FirstDistinctVertex2D(pa, &p2, pa->npoints-1, -1, &pn) )
2336 {
2337 lwgeom_free(cleangeom);
2338 /* This should never happen as we checked the edge while computing first edgend */
2339 lwerror("Invalid clean edge (no two distinct vertices exist) - should not happen");
2340 return -1;
2341 }
2342 lwgeom_free(cleangeom);
2343 if ( ! azimuth_pt_pt(&p2, &pn, &epan.myaz) ) {
2344 lwerror("error computing azimuth of last edgeend [%.15g %.15g,%.15g %.15g]",
2345 p2.x, p2.y, pn.x, pn.y);
2346 return -1;
2347 }
2348 LWDEBUGF(1, "edge's end node is %g,%g", p2.x, p2.y);
2349
2350 /*
2351 * Check endpoints existence, match with Curve geometry
2352 * and get face information (if any)
2353 */
2354
2355 if ( start_node != end_node ) {
2356 num_nodes = 2;
2357 node_ids[0] = start_node;
2358 node_ids[1] = end_node;
2359 } else {
2360 num_nodes = 1;
2361 node_ids[0] = start_node;
2362 }
2363
2364 endpoints = lwt_be_getNodeById( topo, node_ids, &num_nodes, LWT_COL_NODE_ALL );
2365 if (num_nodes == UINT64_MAX)
2366 {
2367 lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
2368 return -1;
2369 }
2370 for ( i=0; i<num_nodes; ++i )
2371 {
2372 LWT_ISO_NODE* node = &(endpoints[i]);
2373 if ( node->containing_face != -1 )
2374 {
2375 if ( newedge.face_left == -1 )
2376 {
2377 newedge.face_left = newedge.face_right = node->containing_face;
2378 }
2379 else if ( newedge.face_left != node->containing_face )
2380 {
2381 _lwt_release_nodes(endpoints, num_nodes);
2382 lwerror("SQL/MM Spatial exception - geometry crosses an edge"
2383 " (endnodes in faces %" LWTFMT_ELEMID " and %" LWTFMT_ELEMID ")",
2384 newedge.face_left, node->containing_face);
2385 }
2386 }
2387
2388 LWDEBUGF(1, "Node %d, with geom %p (looking for %d and %d)",
2389 node->node_id, node->geom, start_node, end_node);
2390 if ( node->node_id == start_node ) {
2391 start_node_geom = node->geom;
2392 }
2393 if ( node->node_id == end_node ) {
2394 end_node_geom = node->geom;
2395 }
2396 }
2397
2398 if ( ! skipChecks )
2399 {
2400 if ( ! start_node_geom )
2401 {
2402 if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
2403 lwerror("SQL/MM Spatial exception - non-existent node");
2404 return -1;
2405 }
2406 else
2407 {
2408 pa = start_node_geom->point;
2409 getPoint2d_p(pa, 0, &pn);
2410 if ( ! p2d_same(&pn, &p1) )
2411 {
2412 if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
2413 lwerror("SQL/MM Spatial exception"
2414 " - start node not geometry start point."
2415 //" - start node not geometry start point (%g,%g != %g,%g).", pn.x, pn.y, p1.x, p1.y
2416 );
2417 return -1;
2418 }
2419 }
2420
2421 if ( ! end_node_geom )
2422 {
2423 if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
2424 lwerror("SQL/MM Spatial exception - non-existent node");
2425 return -1;
2426 }
2427 else
2428 {
2429 pa = end_node_geom->point;
2430 getPoint2d_p(pa, 0, &pn);
2431 if ( ! p2d_same(&pn, &p2) )
2432 {
2433 if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
2434 lwerror("SQL/MM Spatial exception"
2435 " - end node not geometry end point."
2436 //" - end node not geometry end point (%g,%g != %g,%g).", pn.x, pn.y, p2.x, p2.y
2437 );
2438 return -1;
2439 }
2440 }
2441
2442 if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
2443
2444 if ( _lwt_CheckEdgeCrossing( topo, start_node, end_node, geom, 0 ) )
2445 return -1;
2446
2447 } /* ! skipChecks */
2448
2449 /*
2450 * All checks passed, time to prepare the new edge
2451 */
2452
2453 newedge.edge_id = lwt_be_getNextEdgeId( topo );
2454 if ( newedge.edge_id == -1 ) {
2455 lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
2456 return -1;
2457 }
2458
2459 /* Find adjacent edges to each endpoint */
2460 int isclosed = start_node == end_node;
2461 int found;
2462 found = _lwt_FindAdjacentEdges( topo, start_node, &span,
2463 isclosed ? &epan : NULL, -1 );
2464 if ( found ) {
2465 span.was_isolated = 0;
2466 newedge.next_right = span.nextCW ? span.nextCW : -newedge.edge_id;
2467 prev_left = span.nextCCW ? -span.nextCCW : newedge.edge_id;
2468 LWDEBUGF(1, "New edge %d is connected on start node, "
2469 "next_right is %d, prev_left is %d",
2470 newedge.edge_id, newedge.next_right, prev_left);
2471 if ( newedge.face_right == -1 ) {
2472 newedge.face_right = span.cwFace;
2473 }
2474 if ( newedge.face_left == -1 ) {
2475 newedge.face_left = span.ccwFace;
2476 }
2477 } else {
2478 span.was_isolated = 1;
2479 newedge.next_right = isclosed ? -newedge.edge_id : newedge.edge_id;
2480 prev_left = isclosed ? newedge.edge_id : -newedge.edge_id;
2481 LWDEBUGF(1, "New edge %d is isolated on start node, "
2482 "next_right is %d, prev_left is %d",
2483 newedge.edge_id, newedge.next_right, prev_left);
2484 }
2485
2486 found = _lwt_FindAdjacentEdges( topo, end_node, &epan,
2487 isclosed ? &span : NULL, -1 );
2488 if ( found ) {
2489 epan.was_isolated = 0;
2490 newedge.next_left = epan.nextCW ? epan.nextCW : newedge.edge_id;
2491 prev_right = epan.nextCCW ? -epan.nextCCW : -newedge.edge_id;
2492 LWDEBUGF(1, "New edge %d is connected on end node, "
2493 "next_left is %d, prev_right is %d",
2494 newedge.edge_id, newedge.next_left, prev_right);
2495 if ( newedge.face_right == -1 ) {
2496 newedge.face_right = span.ccwFace;
2497 } else if ( modFace != -1 && newedge.face_right != epan.ccwFace ) {
2498 /* side-location conflict */
2499 lwerror("Side-location conflict: "
2500 "new edge starts in face"
2501 " %" LWTFMT_ELEMID " and ends in face"
2502 " %" LWTFMT_ELEMID,
2503 newedge.face_right, epan.ccwFace
2504 );
2505 return -1;
2506 }
2507 if ( newedge.face_left == -1 ) {
2508 newedge.face_left = span.cwFace;
2509 } else if ( modFace != -1 && newedge.face_left != epan.cwFace ) {
2510 /* side-location conflict */
2511 lwerror("Side-location conflict: "
2512 "new edge starts in face"
2513 " %" LWTFMT_ELEMID " and ends in face"
2514 " %" LWTFMT_ELEMID,
2515 newedge.face_left, epan.cwFace
2516 );
2517 return -1;
2518 }
2519 } else {
2520 epan.was_isolated = 1;
2521 newedge.next_left = isclosed ? newedge.edge_id : -newedge.edge_id;
2522 prev_right = isclosed ? -newedge.edge_id : newedge.edge_id;
2523 LWDEBUGF(1, "New edge %d is isolated on end node, "
2524 "next_left is %d, prev_right is %d",
2525 newedge.edge_id, newedge.next_left, prev_right);
2526 }
2527
2528 /*
2529 * If we don't have faces setup by now we must have encountered
2530 * a malformed topology (no containing_face on isolated nodes, no
2531 * left/right faces on adjacent edges or mismatching values)
2532 */
2533 if ( newedge.face_left != newedge.face_right )
2534 {
2535 lwerror("Left(%" LWTFMT_ELEMID ")/right(%" LWTFMT_ELEMID ")"
2536 "faces mismatch: invalid topology ?",
2537 newedge.face_left, newedge.face_right);
2538 return -1;
2539 }
2540 else if ( newedge.face_left == -1 && modFace > -1 )
2541 {
2542 lwerror("Could not derive edge face from linked primitives:"
2543 " invalid topology ?");
2544 return -1;
2545 }
2546
2547 /*
2548 * Insert the new edge, and update all linking
2549 */
2550
2551 int ret = lwt_be_insertEdges(topo, &newedge, 1);
2552 if ( ret == -1 ) {
2553 lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
2554 return -1;
2555 } else if ( ret == 0 ) {
2556 lwerror("Insertion of split edge failed (no reason)");
2557 return -1;
2558 }
2559
2560 int updfields;
2561
2562 /* Link prev_left to us
2563 * (if it's not us already) */
2564 if ( llabs(prev_left) != newedge.edge_id )
2565 {
2566 if ( prev_left > 0 )
2567 {
2568 /* its next_left_edge is us */
2569 updfields = LWT_COL_EDGE_NEXT_LEFT;
2570 updedge.next_left = newedge.edge_id;
2571 seledge.edge_id = prev_left;
2572 }
2573 else
2574 {
2575 /* its next_right_edge is us */
2576 updfields = LWT_COL_EDGE_NEXT_RIGHT;
2577 updedge.next_right = newedge.edge_id;
2578 seledge.edge_id = -prev_left;
2579 }
2580
2581 ret = lwt_be_updateEdges(topo,
2582 &seledge, LWT_COL_EDGE_EDGE_ID,
2583 &updedge, updfields,
2584 NULL, 0);
2585 if ( ret == -1 ) {
2586 lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
2587 return -1;
2588 }
2589 }
2590
2591 /* Link prev_right to us
2592 * (if it's not us already) */
2593 if ( llabs(prev_right) != newedge.edge_id )
2594 {
2595 if ( prev_right > 0 )
2596 {
2597 /* its next_left_edge is -us */
2598 updfields = LWT_COL_EDGE_NEXT_LEFT;
2599 updedge.next_left = -newedge.edge_id;
2600 seledge.edge_id = prev_right;
2601 }
2602 else
2603 {
2604 /* its next_right_edge is -us */
2605 updfields = LWT_COL_EDGE_NEXT_RIGHT;
2606 updedge.next_right = -newedge.edge_id;
2607 seledge.edge_id = -prev_right;
2608 }
2609
2610 ret = lwt_be_updateEdges(topo,
2611 &seledge, LWT_COL_EDGE_EDGE_ID,
2612 &updedge, updfields,
2613 NULL, 0);
2614 if ( ret == -1 ) {
2615 lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
2616 return -1;
2617 }
2618 }
2619
2620 /* NOT IN THE SPECS...
2621 * set containing_face = null for start_node and end_node
2622 * if they where isolated
2623 *
2624 */
2625 LWT_ISO_NODE updnode, selnode;
2626 updnode.containing_face = -1;
2627 if ( span.was_isolated )
2628 {
2629 selnode.node_id = start_node;
2630 ret = lwt_be_updateNodes(topo,
2631 &selnode, LWT_COL_NODE_NODE_ID,
2633 NULL, 0);
2634 if ( ret == -1 ) {
2635 lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
2636 return -1;
2637 }
2638 }
2639 if ( epan.was_isolated )
2640 {
2641 selnode.node_id = end_node;
2642 ret = lwt_be_updateNodes(topo,
2643 &selnode, LWT_COL_NODE_NODE_ID,
2645 NULL, 0);
2646 if ( ret == -1 ) {
2647 lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
2648 return -1;
2649 }
2650 }
2651
2652 /* Check face splitting, if required */
2653
2654 if ( modFace > -1 ) {
2655
2656 if ( ! isclosed && ( epan.was_isolated || span.was_isolated ) )
2657 {
2658 LWDEBUG(1, "New edge is dangling, so it cannot split any face");
2659 return newedge.edge_id; /* no split */
2660 }
2661
2662 int newface1 = -1;
2663
2664 /* IDEA: avoid building edge ring if input is closed, which means we
2665 * know in advance it splits a face */
2666
2667 if ( ! modFace )
2668 {
2669 newface1 = _lwt_AddFaceSplit( topo, -newedge.edge_id, newedge.face_left, 0 );
2670 if ( newface1 == 0 ) {
2671 LWDEBUG(1, "New edge does not split any face");
2672 return newedge.edge_id; /* no split */
2673 }
2674 }
2675
2676 int newface = _lwt_AddFaceSplit( topo, newedge.edge_id,
2677 newedge.face_left, 0 );
2678 if ( modFace )
2679 {
2680 if ( newface == 0 ) {
2681 LWDEBUG(1, "New edge does not split any face");
2682 return newedge.edge_id; /* no split */
2683 }
2684
2685 if ( newface < 0 )
2686 {
2687 /* face on the left is the universe face */
2688 /* must be forming a maximal ring in universal face */
2689 newface = _lwt_AddFaceSplit( topo, -newedge.edge_id,
2690 newedge.face_left, 0 );
2691 if ( newface < 0 ) return newedge.edge_id; /* no split */
2692 }
2693 else
2694 {
2695 _lwt_AddFaceSplit( topo, -newedge.edge_id, newedge.face_left, 1 );
2696 }
2697 }
2698
2699 /*
2700 * Update topogeometries, if needed
2701 */
2702 if ( newedge.face_left != 0 )
2703 {
2704 ret = lwt_be_updateTopoGeomFaceSplit(topo, newedge.face_left,
2705 newface, newface1);
2706 if ( ret == 0 ) {
2707 lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
2708 return -1;
2709 }
2710
2711 if ( ! modFace )
2712 {
2713 /* drop old face from the face table */
2714 ret = lwt_be_deleteFacesById(topo, &(newedge.face_left), 1);
2715 if ( ret == -1 ) {
2716 lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
2717 return -1;
2718 }
2719 }
2720 }
2721
2722 } // end of face split checking
2723
2724 return newedge.edge_id;
2725}
int azimuth_pt_pt(const POINT2D *p1, const POINT2D *p2, double *ret)
Compute the azimuth of segment AB in radians.
Definition measures.c:2461
void lwgeom_free(LWGEOM *geom)
Definition lwgeom.c:1138
int lwgeom_is_simple(const LWGEOM *lwgeom)
int getPoint2d_p(const POINTARRAY *pa, uint32_t n, POINT2D *point)
Definition lwgeom_api.c:349
LWGEOM * lwline_as_lwgeom(const LWLINE *obj)
Definition lwgeom.c:321
LWLINE * lwgeom_as_lwline(const LWGEOM *lwgeom)
Definition lwgeom.c:161
LWGEOM * lwgeom_remove_repeated_points(const LWGEOM *in, double tolerance)
Definition lwgeom.c:1454
int p2d_same(const POINT2D *p1, const POINT2D *p2)
Definition lwalgorithm.c:50
LWT_INT64 LWT_ELEMID
Identifier of topology element.
#define LWT_COL_EDGE_NEXT_RIGHT
#define LWT_COL_NODE_CONTAINING_FACE
#define LWT_COL_EDGE_EDGE_ID
Edge fields.
#define LWT_COL_EDGE_NEXT_LEFT
#define LWT_COL_NODE_NODE_ID
Node fields.
#define LWT_COL_NODE_ALL
#define LWDEBUG(level, msg)
Definition lwgeom_log.h:83
#define LWDEBUGF(level, msg,...)
Definition lwgeom_log.h:88
void lwerror(const char *fmt,...)
Write a notice out to the error handler.
Definition lwutil.c:190
static int lwt_be_deleteFacesById(const LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t numelems)
static int _lwt_FirstDistinctVertex2D(const POINTARRAY *pa, POINT2D *ref, int from, int dir, POINT2D *op)
LWT_ELEMID lwt_be_getNextEdgeId(LWT_TOPOLOGY *topo)
static int lwt_be_updateTopoGeomFaceSplit(LWT_TOPOLOGY *topo, LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2)
static int _lwt_CheckEdgeCrossing(LWT_TOPOLOGY *topo, LWT_ELEMID start_node, LWT_ELEMID end_node, const LWLINE *geom, LWT_ELEMID myself)
static void _lwt_release_nodes(LWT_ISO_NODE *nodes, int num_nodes)
int lwt_be_updateEdges(LWT_TOPOLOGY *topo, const LWT_ISO_EDGE *sel_edge, int sel_fields, const LWT_ISO_EDGE *upd_edge, int upd_fields, const LWT_ISO_EDGE *exc_edge, int exc_fields)
static int _lwt_FindAdjacentEdges(LWT_TOPOLOGY *topo, LWT_ELEMID node, edgeend *data, edgeend *other, int myedge_id)
static LWT_ELEMID _lwt_AddFaceSplit(LWT_TOPOLOGY *topo, LWT_ELEMID sedge, LWT_ELEMID face, int mbr_only)
int lwt_be_insertEdges(LWT_TOPOLOGY *topo, LWT_ISO_EDGE *edge, uint64_t numelems)
const char * lwt_be_lastErrorMessage(const LWT_BE_IFACE *be)
#define LWTFMT_ELEMID
Definition lwgeom_topo.c:43
LWT_ISO_NODE * lwt_be_getNodeById(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields)
static int lwt_be_updateNodes(LWT_TOPOLOGY *topo, const LWT_ISO_NODE *sel_node, int sel_fields, const LWT_ISO_NODE *upd_node, int upd_fields, const LWT_ISO_NODE *exc_node, int exc_fields)
POINTARRAY * points
Definition liblwgeom.h:469
POINTARRAY * point
Definition liblwgeom.h:457
LWT_ELEMID face_right
LWT_ELEMID next_right
LWT_ELEMID end_node
LWT_ELEMID face_left
LWT_ELEMID next_left
LWT_ELEMID edge_id
LWT_ELEMID start_node
LWT_ELEMID node_id
LWT_ELEMID containing_face
LWPOINT * geom
const LWT_BE_IFACE * be_iface
double y
Definition liblwgeom.h:376
double x
Definition liblwgeom.h:376
uint32_t npoints
Definition liblwgeom.h:413
double myaz
LWT_ELEMID nextCCW
LWT_ELEMID ccwFace
LWT_ELEMID cwFace
LWT_ELEMID nextCW

References _lwt_AddFaceSplit(), _lwt_CheckEdgeCrossing(), _lwt_FindAdjacentEdges(), _lwt_FirstDistinctVertex2D(), _lwt_release_nodes(), azimuth_pt_pt(), LWT_TOPOLOGY_T::be_iface, edgeend_t::ccwFace, LWT_ISO_NODE::containing_face, edgeend_t::cwFace, LWT_ISO_EDGE::edge_id, LWT_ISO_EDGE::end_node, LWT_ISO_EDGE::face_left, LWT_ISO_EDGE::face_right, LWT_ISO_NODE::geom, LWT_ISO_EDGE::geom, getPoint2d_p(), LWDEBUG, LWDEBUGF, lwerror(), lwgeom_as_lwline(), lwgeom_free(), lwgeom_is_simple(), lwgeom_remove_repeated_points(), lwline_as_lwgeom(), lwt_be_deleteFacesById(), lwt_be_getNextEdgeId(), lwt_be_getNodeById(), lwt_be_insertEdges(), lwt_be_lastErrorMessage(), lwt_be_updateEdges(), lwt_be_updateNodes(), lwt_be_updateTopoGeomFaceSplit(), LWT_COL_EDGE_EDGE_ID, LWT_COL_EDGE_NEXT_LEFT, LWT_COL_EDGE_NEXT_RIGHT, LWT_COL_NODE_ALL, LWT_COL_NODE_CONTAINING_FACE, LWT_COL_NODE_NODE_ID, LWTFMT_ELEMID, edgeend_t::myaz, LWT_ISO_EDGE::next_left, LWT_ISO_EDGE::next_right, edgeend_t::nextCCW, edgeend_t::nextCW, LWT_ISO_NODE::node_id, POINTARRAY::npoints, p2d_same(), LWPOINT::point, LWLINE::points, LWT_ISO_EDGE::start_node, edgeend_t::was_isolated, POINT2D::x, and POINT2D::y.

Referenced by _lwt_AddLineEdge(), lwt_AddEdgeModFace(), and lwt_AddEdgeNewFaces().

Here is the call graph for this function:
Here is the caller graph for this function: