123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- /****************************************************************
- *
- * MODULE: v.net.steiner
- *
- * AUTHOR(S): Radim Blazek
- *
- * PURPOSE: Find Steiner tree for network
- *
- * COPYRIGHT: (C) 2001 by the GRASS Development Team
- *
- * This program is free software under the
- * GNU General Public License (>=v2).
- * Read the file COPYING that comes with GRASS
- * for details.
- *
- **************************************************************/
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
- #include <grass/gis.h>
- #include <grass/vector.h>
- #include <grass/dbmi.h>
- #include <grass/glocale.h>
- /* costs between 2 terminals */
- typedef struct
- {
- int term1, term2;
- double cost;
- } COST;
- int nterms; /* number of terminals */
- int nnodes; /* number of nodes */
- int *terms; /* array of 1. terminals; 2. accepted steiner points; 3. tested steiner point */
- COST *term_costs; /* costs between terminals */
- COST *sp_costs; /* costs between StP and terminals */
- int *comps; /* numbers of component, the terminal was assigned to */
- /* pointer to array of pointers to costs to other nodes, matrix: from-to, from < to:
- * 1-2 1-3 1-4 ... 1-nnodes
- * 2-3 2-4 ... 2-nnodes
- * 3-4 ... 3-nnodes
- * ...
- * (nnodes - 1)-nnodes
- * !!! init costs for from or to node, before this cost is read by init_node_costs();
- */
- double **nodes_costs;
- int cmp(const void *, const void *);
- /* Init all costs to/from given node */
- int init_node_costs(struct Map_info *Map, int from)
- {
- int to, ret, row, col;
- double cost;
- G_verbose_message(_("Init costs from node %d"), from);
- for (to = 1; to <= nnodes; to++) {
- if (from == to)
- continue;
- ret = Vect_net_shortest_path(Map, from, to, NULL, &cost);
- if (ret == -1) {
- G_debug(1, "Destination node %d is unreachable from node %d\n", to, from);
- cost = -2;
- }
- if (from < to) {
- row = from - 1;
- col = to - from - 1;
- }
- else {
- row = to - 1;
- col = from - to - 1;
- }
- G_debug(3, "init costs %d - > %d = %f\n", from, to, cost);
- nodes_costs[row][col] = cost;
- }
- return 1;
- }
- /* Get cost from node to node.
- * From or to costs must be init before!!!
- * Returns: 1 - ok, 0 - not reachable
- */
- int get_node_costs(int from, int to, double *cost)
- {
- int row, col, tmp;
- if (from == to) {
- *cost = 0;
- return 1;
- }
- if (from > to) {
- tmp = from;
- from = to;
- to = tmp;
- }
- row = from - 1;
- col = to - from - 1;
- if (nodes_costs[row][col] == -2) {
- *cost = PORT_DOUBLE_MAX;
- return 0;
- }
- *cost = nodes_costs[row][col];
- return 1;
- }
- /* Calculate costs for MST on given set of terminals.
- * If AList / NList is not NULL, list of arcs / nodes in MST is created
- * Note: qsort() from more (say >30) terminals, takes long time (most of mst()).
- * To improve the speed, there are used two sorted queues of costs:
- * 1. for all combinations of in trms
- * 2. from 'sp' to all other terminals
- * Because 1. is sorted only if new sp as added to list of terminals,
- * and 2. is much shorter than 1., a lot of time is saved.
- */
- int mst(struct Map_info *Map, int *trms, int ntrms, /* array of terminal, number of terminals */
- double *cst, double max_cst, /* cost, maximum cost */
- struct ilist *AList, struct ilist *NList, /* list of arcs/nodes in ST */
- int sp, /* Steiner point (node) to be tested with terminals, (0 = ignore) */
- int rebuild)
- { /* rebuild the sorted list of costs for terminals */
- int i, j, node1, node2, com1, com2, t1, t2, line;
- static int k;
- int tcpos, scpos; /* current position in the term_costs / sp_costs */
- double tcst;
- struct ilist *List;
- int nsteps, quse;
- int nall; /* number of terminals + sp ( if used ) */
- if (AList != NULL) {
- Vect_reset_list(AList);
- }
- List = Vect_new_list();
- /* Create sorted array for all combinations of terms */
- if (rebuild) {
- k = 0;
- for (i = 0; i < ntrms; i++) {
- for (j = i + 1; j < ntrms; j++) {
- term_costs[k].term1 = i;
- term_costs[k].term2 = j;
- get_node_costs(trms[i], trms[j], &tcst);
- term_costs[k].cost = tcst;
- k++;
- }
- }
- qsort((void *)term_costs, k, sizeof(COST), cmp); /* this takes most of a time in mst() */
- for (i = 0; i < k; i++) {
- G_debug(3, " %d - %d cost = %f\n", term_costs[i].term1,
- term_costs[i].term2, term_costs[i].cost);
- }
- }
- /* Create sorted array for all combinations of sp -> terms */
- if (sp > 0) {
- for (i = 0; i < ntrms; i++) {
- sp_costs[i].term1 = -1; /* not needed */
- sp_costs[i].term2 = i;
- get_node_costs(sp, trms[i], &tcst);
- sp_costs[i].cost = tcst;
- }
- qsort((void *)sp_costs, ntrms, sizeof(COST), cmp);
- for (i = 0; i < ntrms; i++) {
- G_debug(3, " %d - %d cost = %f\n", sp_costs[i].term1,
- sp_costs[i].term2, sp_costs[i].cost);
- }
- }
- tcst = 0;
- /* MST has number_of_terminals-1 arcs */
- if (sp > 0) {
- nall = ntrms + 1;
- nsteps = ntrms; /* i.e. + one StP */
- }
- else {
- nall = ntrms;
- nsteps = ntrms - 1;
- }
- G_debug(1, "nall = %d\n", nall);
- for (i = 0; i < nall; i++)
- comps[i] = 0;
- tcpos = 0;
- scpos = 0;
- G_debug(2, "nsteps = %d\n", nsteps);
- for (i = 0; i < nsteps; i++) {
- G_debug(2, "step = %d\n", i);
- /* Take the best (lowest costs, no cycle) from both queues */
- /* For both queues go to next lowest costs without cycle */
- /* treminal costs */
- for (j = tcpos; j < k; j++) {
- t1 = term_costs[j].term1;
- t2 = term_costs[j].term2;
- com1 = comps[t1];
- com2 = comps[t2];
- if (com1 != com2 || com1 == 0) { /* will not create cycle -> candidate */
- tcpos = j;
- break;
- }
- }
- if (j == k) { /* arc without cycle not found */
- tcpos = -1;
- }
- /* StP costs */
- if (sp > 0) {
- for (j = scpos; j < ntrms; j++) {
- t1 = ntrms; /* StP is on first fre position */
- t2 = sp_costs[j].term2;
- com1 = comps[t1];
- com2 = comps[t2];
- G_debug(3, "scpos: j = %d comps(%d) = %d coms(%d) = %d\n", j,
- t1, com1, t2, com2);
- if (com1 != com2 || com1 == 0) { /* will not create cycle -> candidate */
- scpos = j;
- G_debug(3, " ok -> scpos = %d\n", scpos);
- break;
- }
- }
- if (j == ntrms) { /* arc without cycle not found */
- scpos = -1;
- }
- }
- else {
- scpos = -1;
- }
- /* Do not access invalid items even for debugging */
- if (tcpos != -1 && scpos != -1)
- G_debug(3, "tcost = %f, scost = %f\n", term_costs[tcpos].cost,
- sp_costs[scpos].cost);
- /* Now we have positions set on lowest costs in each queue or -1 if no more/not used */
- if (tcpos >= 0 && scpos >= 0) {
- if (term_costs[tcpos].cost < sp_costs[scpos].cost)
- quse = 1; /* use terms queue */
- else
- quse = 2; /* use sp queue */
- }
- else if (tcpos >= 0) {
- quse = 1; /* use terms queue */
- }
- else {
- quse = 2; /* use sp queue */
- }
- /* Now we know from which queue take next arc -> add arc to components */
- if (quse == 1) {
- t1 = term_costs[tcpos].term1;
- t2 = term_costs[tcpos].term2;
- tcst += term_costs[tcpos].cost;
- tcpos++;
- }
- else {
- t1 = ntrms;
- t2 = sp_costs[scpos].term2;
- tcst += sp_costs[scpos].cost;
- scpos++;
- }
- G_debug(3, "quse = %d t1 = %d t2 = %d\n", quse, t1, t2);
- G_debug(3, "tcst = %f (max = %f)\n", tcst, max_cst);
- com1 = comps[t1];
- com2 = comps[t2];
- comps[t1] = i + 1;
- comps[t2] = i + 1;
- G_debug(3, "comps(%d) = %d coms(%d) = %d\n", t1, i + 1, t2, i + 1);
- /* reset connected branches */
- for (j = 0; j < nall; j++) {
- if (comps[j] == com1 && com1 != 0)
- comps[j] = i + 1;
- if (comps[j] == com2 && com2 != 0)
- comps[j] = i + 1;
- }
- if (tcst > max_cst) {
- G_debug(3, "cost > max -> return\n");
- *cst = PORT_DOUBLE_MAX;
- return 1;
- }
- /* add to list of arcs */
- if (AList != NULL) {
- node1 = trms[t1];
- node2 = trms[t2];
- Vect_net_shortest_path(Map, node1, node2, List, NULL);
- for (j = 0; j < List->n_values; j++) {
- Vect_list_append(AList, abs(List->value[j]));
- }
- }
- }
- /* create list of nodes */
- if (NList != NULL) {
- Vect_reset_list(NList);
- for (i = 0; i < AList->n_values; i++) {
- line = AList->value[i];
- Vect_get_line_nodes(Map, line, &node1, &node2);
- Vect_list_append(NList, node1);
- Vect_list_append(NList, node2);
- }
- }
- *cst = tcst;
- Vect_destroy_list(List);
- return 1;
- }
- int main(int argc, char **argv)
- {
- int i, j, k, ret;
- int nlines, type, ltype, afield, tfield, geo, cat;
- int sp, nsp, nspused, node, line;
- struct Option *map, *output, *afield_opt, *tfield_opt, *afcol, *type_opt,
- *term_opt, *nsp_opt;
- struct Flag *geo_f;
- struct GModule *module;
- struct Map_info Map, Out;
- int *testnode; /* array all nodes: 1 - should be tested as Steiner,
- * 0 - no need to test (unreachable or terminal) */
- struct ilist *TList; /* list of terminal nodes */
- struct ilist *StArcs; /* list of arcs on Steiner tree */
- struct ilist *StNodes; /* list of nodes on Steiner tree */
- struct boxlist *pointlist;
- double cost, tmpcost;
- struct cat_list *Clist;
- struct line_cats *Cats;
- struct line_pnts *Points;
-
- /* Initialize the GIS calls */
- G_gisinit(argv[0]);
- module = G_define_module();
- G_add_keyword(_("vector"));
- G_add_keyword(_("network"));
- G_add_keyword(_("steiner tree"));
- module->label =
- _("Creates Steiner tree for the network and given terminals.");
- module->description =
- _("Note that 'Minimum Steiner Tree' problem is NP-hard "
- "and heuristic algorithm is used in this module so "
- "the result may be sub optimal.");
- map = G_define_standard_option(G_OPT_V_INPUT);
- output = G_define_standard_option(G_OPT_V_OUTPUT);
- type_opt = G_define_standard_option(G_OPT_V_TYPE);
- type_opt->key = "arc_type";
- type_opt->options = "line,boundary";
- type_opt->answer = "line,boundary";
- type_opt->label = _("Arc type");
- afield_opt = G_define_standard_option(G_OPT_V_FIELD);
- afield_opt->key = "arc_layer";
- afield_opt->answer = "1";
- afield_opt->label = _("Arc layer");
- tfield_opt = G_define_standard_option(G_OPT_V_FIELD);
- tfield_opt->key = "node_layer";
- tfield_opt->answer = "2";
- tfield_opt->label = _("Node layer (used for terminals)");
- afcol = G_define_option();
- afcol->key = "acolumn";
- afcol->type = TYPE_STRING;
- afcol->required = NO;
- afcol->description = _("Arcs' cost column (for both directions)");
- term_opt = G_define_standard_option(G_OPT_V_CATS);
- term_opt->key = "terminal_cats";
- term_opt->required = YES;
- term_opt->description =
- _("Categories of points on terminals (layer is specified by nlayer)");
- nsp_opt = G_define_option();
- nsp_opt->key = "npoints";
- nsp_opt->type = TYPE_INTEGER;
- nsp_opt->required = NO;
- nsp_opt->multiple = NO;
- nsp_opt->answer = "-1";
- nsp_opt->description = _("Number of Steiner points (-1 for all possible)");
- geo_f = G_define_flag();
- geo_f->key = 'g';
- geo_f->description =
- _("Use geodesic calculation for longitude-latitude locations");
- if (G_parser(argc, argv))
- exit(EXIT_FAILURE);
- Cats = Vect_new_cats_struct();
- Points = Vect_new_line_struct();
- type = Vect_option_to_types(type_opt);
- afield = atoi(afield_opt->answer);
- TList = Vect_new_list();
- StArcs = Vect_new_list();
- StNodes = Vect_new_list();
- Clist = Vect_new_cat_list();
- tfield = atoi(tfield_opt->answer);
- Vect_str_to_cat_list(term_opt->answer, Clist);
- G_debug(1, "Imput categories:\n");
- for (i = 0; i < Clist->n_ranges; i++) {
- G_debug(1, "%d - %d\n", Clist->min[i], Clist->max[i]);
- }
- if (geo_f->answer)
- geo = 1;
- else
- geo = 0;
- Vect_check_input_output_name(map->answer, output->answer, G_FATAL_EXIT);
- Vect_set_open_level(2);
- if (Vect_open_old(&Map, map->answer, "") < 0)
- G_fatal_error(_("Unable to open vector map <%s>"), map->answer);
- nnodes = Vect_get_num_nodes(&Map);
- nlines = Vect_get_num_lines(&Map);
- /* Create list of terminals based on list of categories */
- for (i = 1; i <= nlines; i++) {
- ltype = Vect_get_line_type(&Map, i);
- if (!(ltype & GV_POINT))
- continue;
- Vect_read_line(&Map, Points, Cats, i);
- if (!(Vect_cat_get(Cats, tfield, &cat)))
- continue;
- node = Vect_find_node(&Map, Points->x[0], Points->y[0], Points->z[0], 0, 0);
- if (!node) {
- G_warning(_("Point is not connected to the network (cat=%d)"), cat);
- continue;
- }
- if (Vect_cat_in_cat_list(cat, Clist)) {
- Vect_list_append(TList, node);
- }
- }
- nterms = TList->n_values;
- /* GTC Terminal refers to an Steiner tree endpoint */
- G_message(_("Number of terminals: %d\n"), nterms);
- if (nterms < 2) {
- /* GTC Terminal refers to an Steiner tree endpoint */
- G_fatal_error(_("Not enough terminals (< 2)"));
- }
- /* Number of steiner points */
- nsp = atoi(nsp_opt->answer);
- if (nsp > nterms - 2) {
- nsp = nterms - 2;
- G_warning(_("Requested number of Steiner points > than possible"));
- }
- else if (nsp == -1) {
- nsp = nterms - 2;
- }
- G_message(_("Number of Steiner points set to %d\n"), nsp);
- testnode = (int *)G_malloc((nnodes + 1) * sizeof(int));
- for (i = 1; i <= nnodes; i++)
- testnode[i] = 1;
- /* Alloc arrays of costs for nodes, first node at 1 (0 not used) */
- nodes_costs = (double **)G_malloc((nnodes) * sizeof(double *));
- for (i = 0; i < nnodes; i++) {
- nodes_costs[i] =
- (double *)G_malloc((nnodes - i) * sizeof(double));
- for (j = 0; j < nnodes - i; j++)
- nodes_costs[i][j] = -1; /* init, i.e. cost was not calculated yet */
- }
- /* alloc memory from each to each other (not directed) terminal */
- i = nterms + nterms - 2; /* max number of terms + Steiner points */
- comps = (int *)G_malloc(i * sizeof(int));
- i = i * (i - 1) / 2; /* number of combinations */
- term_costs = (COST *) G_malloc(i * sizeof(COST));
- /* alloc memory for costs from Stp to each other terminal */
- i = nterms + nterms - 2 - 1; /* max number of terms + Steiner points - 1 */
- sp_costs = (COST *) G_malloc(i * sizeof(COST));
- terms = (int *)G_malloc((nterms + nterms - 2) * sizeof(int)); /* i.e. +(nterms - 2) St Points */
- /* Create initial parts from list of terminals */
- G_debug(1, "List of terminal nodes (%d):\n", nterms);
- for (i = 0; i < nterms; i++) {
- G_debug(1, "%d\n", TList->value[i]);
- terms[i] = TList->value[i];
- testnode[terms[i]] = 0; /* do not test as Steiner */
- }
- /* Build graph */
- Vect_net_build_graph(&Map, type, afield, 0, afcol->answer, NULL, NULL,
- geo, 0);
- /* Init costs for all terminals */
- for (i = 0; i < nterms; i++)
- init_node_costs(&Map, terms[i]);
- /* Test if all terminal may be connected */
- for (i = 1; i < nterms; i++) {
- ret = get_node_costs(terms[0], terms[i], &cost);
- if (ret == 0) {
- /* GTC Terminal refers to an Steiner tree endpoint */
- G_fatal_error(_("Terminal at node [%d] cannot be connected "
- "to terminal at node [%d]"), terms[0], terms[i]);
- }
- }
- /* Remove not reachable from list of SP candidates */
- j = 0;
- for (i = 1; i <= nnodes; i++) {
- ret = get_node_costs(terms[0], i, &cost);
- if (ret == 0) {
- testnode[i] = 0;
- G_debug(2, "node %d removed from list of Steiner point candidates\n", i );
- j++;
- }
- }
- G_message(_("[%d] (not reachable) nodes removed from list "
- "of Steiner point candidates"), j);
- /* calc costs for terminals MST */
- ret = mst(&Map, terms, nterms, &cost, PORT_DOUBLE_MAX, NULL, NULL, 0, 1); /* no StP, rebuild */
- G_message(_("MST costs = %f"), cost);
- /* Go through all nodes and try to use as steiner points -> find that which saves most costs */
- nspused = 0;
- for (j = 0; j < nsp; j++) {
- sp = 0;
- G_verbose_message(_("Search for [%d]. Steiner point"), j + 1);
- for (i = 1; i <= nnodes; i++) {
- G_percent(i, nnodes, 1);
- if (testnode[i] == 0) {
- G_debug(3, "skip test for %d\n", i);
- continue;
- }
- ret =
- mst(&Map, terms, nterms + j, &tmpcost, cost, NULL, NULL, i,
- 0);
- G_debug(2, "cost = %f x %f\n", tmpcost, cost);
- if (tmpcost < cost) { /* sp candidate */
- G_debug(3,
- " steiner candidate node = %d mst = %f (x last = %f)\n",
- i, tmpcost, cost);
- sp = i;
- cost = tmpcost;
- }
- }
- if (sp > 0) {
- G_message(_("Steiner point at node [%d] was added "
- "to terminals (MST costs = %f)"), sp, cost);
- terms[nterms + j] = sp;
- init_node_costs(&Map, sp);
- testnode[sp] = 0;
- nspused++;
- /* rebuild for nex cycle */
- ret =
- mst(&Map, terms, nterms + nspused, &tmpcost, PORT_DOUBLE_MAX,
- NULL, NULL, 0, 1);
- }
- else { /* no steiner found */
- G_message(_("No Steiner point found -> leaving cycle"));
- break;
- }
- }
- G_message(_("Number of added Steiner points: %d "
- "(theoretic max is %d).\n"), nspused, nterms - 2);
- /* Build lists of arcs and nodes for final version */
- ret =
- mst(&Map, terms, nterms + nspused, &cost, PORT_DOUBLE_MAX, StArcs,
- StNodes, 0, 0);
- /* Calculate true costs, which may be lower than MST if steiner points were not used */
- if (nsp < nterms - 2) {
- G_message(_("Spanning tree costs on complete graph = %f\n"
- "(may be higher than resulting Steiner tree costs!!!)"),
- cost);
- }
- else
- G_message(_("Steiner tree costs = %f"), cost);
- /* Write arcs to new map */
- if (Vect_open_new(&Out, output->answer, Vect_is_3d(&Map)) < 0)
- G_fatal_error(_("Unable to create vector map <%s>"), output->answer);
- Vect_hist_command(&Out);
- G_debug(1, "Steiner tree:");
- G_debug(1, "Arcs' categories (layer %d, %d arcs):", afield,
- StArcs->n_values);
-
- for (i = 0; i < StArcs->n_values; i++) {
- line = StArcs->value[i];
- ltype = Vect_read_line(&Map, Points, Cats, line);
- Vect_write_line(&Out, ltype, Points, Cats);
- Vect_cat_get(Cats, afield, &cat);
- G_debug(1, "arc cat = %d", cat);
- }
-
- G_debug(1, "Nodes' categories (layer %d, %d nodes):", tfield,
- StNodes->n_values);
- k = 0;
- pointlist = Vect_new_boxlist(0);
- for (i = 0; i < StNodes->n_values; i++) {
- double x, y, z;
- struct bound_box box;
-
- node = StNodes->value[i];
-
- Vect_get_node_coor(&Map, node, &x, &y, &z);
- box.E = box.W = x;
- box.N = box.S = y;
- box.T = box.B = z;
- Vect_select_lines_by_box(&Map, &box, GV_POINT, pointlist);
-
- nlines = Vect_get_node_n_lines(&Map, node);
- for (j = 0; j < pointlist->n_values; j++) {
- line = pointlist->id[j];
- ltype = Vect_read_line(&Map, Points, Cats, line);
- if (!(ltype & GV_POINT))
- continue;
- if (!(Vect_cat_get(Cats, tfield, &cat)))
- continue;
- Vect_write_line(&Out, ltype, Points, Cats);
- G_debug(1, "node cat = %d", cat);
- k++;
- }
- }
- Vect_build(&Out);
- G_message(n_("A Steiner tree with %d arc has been built",
- "A Steiner tree with %d arcs has been built", StArcs->n_values),
- StArcs->n_values);
-
- /* Free, ... */
- Vect_destroy_list(StArcs);
- Vect_destroy_list(StNodes);
- Vect_close(&Map);
- Vect_close(&Out);
- exit(EXIT_SUCCESS);
- }
- int cmp(const void *pa, const void *pb)
- {
- COST *p1 = (COST *) pa;
- COST *p2 = (COST *) pb;
- if (p1->cost < p2->cost)
- return -1;
- if (p1->cost > p2->cost)
- return 1;
- return 0;
- }
|