Beispiel #1
0
//---------------------------------------------------------------------------
int enviar_infoReduce_job(int socket_job,t_reduce_dest* reduce_dest, t_reduce_nodo_dest* nodo_host) {
	int result = 1;

	t_buffer* reduce_to_Nodo_buff;
	reduce_to_Nodo_buff = buffer_create_with_protocol(EXECUTE_REDUCE);

	buffer_add_int(reduce_to_Nodo_buff, nodo_host->id_nodo);
	buffer_add_string(reduce_to_Nodo_buff, reduce_dest->temp_file_name);
	buffer_add_int(reduce_to_Nodo_buff, list_size(nodo_host->path_temps));

	void _buffer_add_path(char* path) {
		buffer_add_string(reduce_to_Nodo_buff, path);
	}
Beispiel #2
0
/*
 * Return the column's name matching the specified number from table
 * (Only use in specific FE position function, so not directly inside
 *  storage handle mechanism)
 */
buffer *ows_psql_column_name(ows * o, buffer * layer_name, int number)
{
    buffer *sql;
    PGresult *res;
    buffer *column;

    assert(o);
    assert(layer_name);

    sql = buffer_init();
    column = buffer_init();

    buffer_add_str(sql, "SELECT a.attname FROM pg_class c, pg_attribute a, pg_type t WHERE c.relname ='");
    buffer_copy(sql, layer_name);
    buffer_add_str(sql, "' AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid AND a.attnum = ");
    buffer_add_int(sql, number);

    res = ows_psql_exec(o, sql->buf);
    buffer_free(sql);

    if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) != 1) {
        PQclear(res);
        return column;
    }

    buffer_add_str(column, PQgetvalue(res, 0, 0));
    PQclear(res);

    return column;
}
Beispiel #3
0
/*
 * Retrieve a srs from a srid
 */
buffer *ows_srs_get_from_a_srid(ows * o, int srid)
{
  buffer *b;
  buffer *sql;
  PGresult *res;

  assert(o);

  sql = buffer_init();
  buffer_add_str(sql, "SELECT auth_name||':'||auth_srid AS srs ");
  buffer_add_str(sql, "FROM spatial_ref_sys ");
  buffer_add_str(sql, "WHERE srid=");
  buffer_add_int(sql, srid);

  res = ows_psql_exec(o, sql->buf);
  buffer_free(sql);

  b = buffer_init();

  if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) != 1) {
    PQclear(res);
    return b;
  }

  buffer_add_str(b, PQgetvalue(res, 0, 0));

  PQclear(res);

  return b;
}
Beispiel #4
0
/*
 * Set projection value into srs structure
 */
bool ows_srs_set_from_srid(ows * o, ows_srs * s, int srid)
{
  PGresult *res;
  buffer *sql;

  assert(o);
  assert(s);

  if (srid == -1 || srid == 0) {
    s->srid = -1;
    buffer_empty(s->auth_name);
    s->auth_srid = 0;
    s->is_degree = true;
    s->is_reverse_axis = false;
    s->is_eastern_axis = false;

    return true;
  }

  sql = buffer_init();
  buffer_add_str(sql, "SELECT auth_name, auth_srid, ");
  buffer_add_str(sql, "position('+units=m ' in proj4text), ");
  buffer_add_str(sql, "(position('AXIS[\"X\",NORTH]]' in srtext) + position('AXIS[\"Northing\",NORTH]]' in srtext)) ");
  buffer_add_str(sql, "FROM spatial_ref_sys WHERE srid = '");
  buffer_add_int(sql, srid);
  buffer_add_str(sql, "'");

  res = ows_psql_exec(o, sql->buf);
  buffer_free(sql);

  /* If query dont return exactly 1 result, it mean projection not handled */
  if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) != 1) {
    PQclear(res);
    return false;
  }

  buffer_add_str(s->auth_name, PQgetvalue(res, 0, 0));
  s->auth_srid = atoi(PQgetvalue(res, 0, 1));
  s->srid = srid;

  /* Such a way to know if units is meter or degree */
  if (atoi(PQgetvalue(res, 0, 2)) == 0)
    s->is_degree = true;
  else
    s->is_degree = false;

  /* Is easting-northing SRID ? */
  if (atoi(PQgetvalue(res, 0, 3)) != 0)
    s->is_eastern_axis = true;

  PQclear(res);
  return true;
}
Beispiel #5
0
//---------------------------------------------------------------------------
int enviar_infoMap_job(int socket_job,t_map_dest* map_dest){
	int result = 1;

	t_buffer* map_to_Nodo_buff;
	map_to_Nodo_buff = buffer_create_with_protocol(EXECUTE_MAP);


	buffer_add_int(map_to_Nodo_buff, map_dest->id_nodo);
	buffer_add_int(map_to_Nodo_buff, map_dest->block);
	buffer_add_string(map_to_Nodo_buff, map_dest->temp_file_name);
	result = send_buffer_and_destroy(socket_job, map_to_Nodo_buff);

	result = (result > 0) ? send_entire_file_by_parts(socket_job, conf->path_map, MAX_PART_SIZE) : result;


	if(result<= 0) {
		log_error(paranoid_log, "No se pudo enviar Instrucciones de Map");
	} else {
		log_info(paranoid_log, "Se envio correctamente Instrucciones de Map");
	}

	return result;
}
Beispiel #6
0
/*
 * Convert a bbox to PostGIS query Polygon
 */
void ows_bbox_to_query(ows *o, ows_bbox *bbox, buffer *query)
{
    double x1, y1, x2, y2;

    assert(o);
    assert(bbox);
    assert(query);

    if (bbox->srs->is_reverse_axis) {
        x1 = bbox->ymin;
        y1 = bbox->xmin;
        x2 = bbox->ymax;
        y2 = bbox->xmax;
    } else {
        x1 = bbox->xmin;
        y1 = bbox->ymin;
        x2 = bbox->xmax;
        y2 = bbox->ymax;
    }

   /* We use explicit POLYGON geometry rather than BBOX 
      related to precision handle (Float4 vs Double)    */
    buffer_add_str(query, "'SRID=");
    buffer_add_int(query, bbox->srs->srid);
    buffer_add_str(query, ";POLYGON((");
    buffer_add_double(query, x1);
    buffer_add_str(query, " ");
    buffer_add_double(query, y1);
    buffer_add_str(query, ",");
    buffer_add_double(query, x1);
    buffer_add_str(query, " ");
    buffer_add_double(query, y2);
    buffer_add_str(query, ",");
    buffer_add_double(query, x2);
    buffer_add_str(query, " ");
    buffer_add_double(query, y2);
    buffer_add_str(query, ",");
    buffer_add_double(query, x2);
    buffer_add_str(query, " ");
    buffer_add_double(query, y1);
    buffer_add_str(query, ",");
    buffer_add_double(query, x1);
    buffer_add_str(query, " ");
    buffer_add_double(query, y1);
    buffer_add_str(query, "))'::geometry");

    /* FIXME what about geography ? */
}
Beispiel #7
0
/*
 * Set projection value into srs structure
 */
bool ows_srs_set(ows * o, ows_srs * c, const buffer * auth_name, int auth_srid)
{
  PGresult *res;
  buffer *sql;

  assert(o);
  assert(c);
  assert(o->pg);
  assert(auth_name);

  sql = buffer_init();
  buffer_add_str(sql, "SELECT srid, position('+units=m ' in proj4text)");
  buffer_add_str(sql, ", (position('AXIS[\"X\",NORTH]]' in srtext) + position('AXIS[\"Northing\",NORTH]]' in srtext))");
  buffer_add_str(sql, " FROM spatial_ref_sys WHERE auth_name='");
  buffer_copy(sql, auth_name);
  buffer_add_str(sql, "' AND auth_srid=");
  buffer_add_int(sql, auth_srid);

  res = ows_psql_exec(o, sql->buf);
  buffer_free(sql);

  /* If query dont return exactly 1 result, it means projection is not handled */
  if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) != 1) {
    PQclear(res);
    return false;
  }

  buffer_empty(c->auth_name);
  buffer_copy(c->auth_name, auth_name);
  c->auth_srid = auth_srid;

  c->srid = atoi(PQgetvalue(res, 0, 0));

  /* Such a way to know if units is meter or degree */
  if (atoi(PQgetvalue(res, 0, 1)) == 0)
    c->is_degree = true;
  else
    c->is_degree = false;

  /* Is easting-northing SRID ? */
  if (atoi(PQgetvalue(res, 0, 2)) != 0)
    c->is_eastern_axis = true;

  PQclear(res);
  return true;
}
Beispiel #8
0
//---------------------------------------------------------------------------
void hilo_map_job(t_map_dest* map_dest) {

	t_buffer* result_map_buff;
	char *ip_nodo = from_int_to_inet_addr(map_dest->ip_nodo);
	int result;

	log_info(paranoid_log, "Realizando Operación de Map ID:%i, en Nodo: %i, IP: %s, Port: %i, Block: %i Temp: %s",
			map_dest->id, map_dest->id_nodo, ip_nodo, map_dest->puerto_nodo, map_dest->block,
			map_dest->temp_file_name);

	int socket_nodo = solicitarConexionConNodo(ip_nodo, map_dest->puerto_nodo, map_dest->id_nodo);

	result = (socket_nodo != -1) ? enviar_infoMap_job(socket_nodo, map_dest) : -1;
	uint32_t answer_map = 0;
	result = (result > 0) ? receive_answer_map(socket_nodo, &answer_map) : result;

	if(socket_nodo != -1) {
		if(result > 0) {
			if(answer_map == INCORRECT_NODO) {
				result_map_buff = buffer_create_with_protocol(NODO_NOT_FOUND);
			} else {
				result_map_buff = buffer_create_with_protocol(answer_map);
			}
			close(socket_nodo);
		} else {
			result_map_buff = buffer_create_with_protocol(ERROR_IN_CONNECTION);
		}
	} else {
		result_map_buff = buffer_create_with_protocol(NODO_NOT_FOUND);
	}
	buffer_add_int(result_map_buff,map_dest->id);
	pthread_mutex_lock(&conex_marta_ready);
	result = send_buffer_and_destroy(socket_marta,result_map_buff);
	pthread_mutex_unlock(&conex_marta_ready);

	if(result <= 0) {
		log_error(paranoid_log,"No se pudo enviar respuesta de Map a MaRTA");
	}

	free(ip_nodo);
	free_map_dest(map_dest);
}
Beispiel #9
0
/*
 * Transform a GML geometry to PostGIS EWKT
 * Return NULL on error
 */
buffer * ows_psql_gml_to_sql(ows * o, xmlNodePtr n, int srid)
{
    PGresult *res;
    xmlNodePtr g;
    buffer *result, *sql, *gml;

    assert(o);
    assert(n);

    g = ows_psql_recursive_parse_gml(o, n, NULL);
    if (!g) return NULL;    /* No Geometry founded in GML doc */

    /* Retrieve the sub doc and launch GML parse via PostGIS */
    gml = buffer_init();
    cgi_add_xml_into_buffer(gml, g);

    sql = buffer_init();
    buffer_add_str(sql, "SELECT ST_GeomFromGML('");
    buffer_add_str(sql, gml->buf);

    if (ows_version_get(o->postgis_version) >= 200) {
        buffer_add_str(sql, "',");
        buffer_add_int(sql, srid);
        buffer_add_str(sql, ")");
    } else {
        /* Means PostGIS 1.5 */
        buffer_add_str(sql, "')");
    }

    res = ows_psql_exec(o, sql->buf);
    buffer_free(gml);

    /* GML Parse errors cases */
    if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) != 1) {
        buffer_free(sql);
        PQclear(res);
        return NULL;
    }

    result = buffer_init();
    buffer_add_str(result, PQgetvalue(res, 0, 0));
    PQclear(res);

    /* Check if geometry is valid */
    if (o->check_valid_geom) {

        buffer_empty(sql);
        buffer_add_str(sql, "SELECT ST_IsValid('");
        buffer_add_str(sql, result->buf);
        buffer_add_str(sql, "')");

        res = ows_psql_exec(o, sql->buf);

        if (    PQresultStatus(res) != PGRES_TUPLES_OK
                || PQntuples(res) != 1
                || (char) PQgetvalue(res, 0, 0)[0] !=  't') {
            buffer_free(sql);
            buffer_free(result);
            PQclear(res);
            return NULL;
        }
        PQclear(res);
    }

    buffer_free(sql);

    return result;
}
Beispiel #10
0
/*
 * Generate a new buffer id supposed to be unique for a given layer name
 */
buffer *ows_psql_generate_id(ows * o, buffer * layer_name)
{
    ows_layer_node *ln;
    buffer * id, *sql_id;
    FILE *fp;
    PGresult * res;
    int i, seed_len;
    char * seed = NULL;

    assert(o);
    assert(o->layers);
    assert(layer_name);

    /* Retrieve layer node pointer */
    for (ln = o->layers->first ; ln ; ln = ln->next) {
        if (ln->layer->name && ln->layer->storage
                && !strcmp(ln->layer->name->buf, layer_name->buf)) break;
    }
    assert(ln);

    id = buffer_init();

    /* If PK have a sequence in PostgreSQL database,
     * retrieve next available sequence value
     */
    if (ln->layer->storage->pkey_sequence) {
        sql_id = buffer_init();
        buffer_add_str(sql_id, "SELECT nextval('");
        buffer_copy(sql_id, ln->layer->storage->pkey_sequence);
        buffer_add_str(sql_id, "');");
        res = ows_psql_exec(o, sql_id->buf);
        buffer_free(sql_id);

        if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1) {
            buffer_add_str(id, (char *) PQgetvalue(res, 0, 0));
            PQclear(res);
            return id;
        }

        /* FIXME: Shouldn't we return an error there instead ? */
        PQclear(res);
    }

    /*
     * If we don't have a PostgreSQL Sequence, we will try to
     * generate a pseudo random keystring using /dev/urandom
     * Will so work only on somes/commons Unix system
     */
    seed_len = 6;
    seed = malloc(sizeof(char) * (seed_len * 3 + 1));  /* multiply by 3 to be able to deal
                                                           with hex2dec conversion */
    assert(seed);
    seed[0] = '\0';

    fp = fopen("/dev/urandom","r");
    if (fp) {
        for (i=0 ; i<seed_len ; i++)
            sprintf(seed,"%s%03d", seed, fgetc(fp));
        fclose(fp);
        buffer_add_str(id, seed);
        free(seed);

        return id;
    }
    free(seed);

    /* Case where we not using PostgreSQL sequence,
     * and OS don't have a /dev/urandom support
     * This case don't prevent to produce ID collision
     * Don't use it unless really no others choices !!!
     */
    srand((int) (time(NULL) ^ rand() % 1000) + 42);
    srand((rand() % 1000 ^ rand() % 1000) + 42);
    buffer_add_int(id, rand());

    return id;
}
Beispiel #11
0
//---------------------------------------------------------------------------
void hilo_reduce_job(t_reduce_dest* reduce_dest) {

	int result = 1;
	uint32_t answer_reduce = 0;

	mostrar_reduce_dest(reduce_dest);

	int _isNodoHost(t_reduce_nodo_dest* nodo) {
		return reduce_dest->id_nodo_host == nodo->id_nodo;
	}
	t_reduce_nodo_dest* nodo_host = list_remove_by_condition(reduce_dest->list_nodos, (void *)_isNodoHost);

	char *ip_nodo = from_int_to_inet_addr(nodo_host->ip_nodo);
	int socket_nodo = solicitarConexionConNodo(ip_nodo, nodo_host->puerto_nodo, nodo_host->id_nodo);

	if(socket_nodo != 1) {
		result = enviar_infoReduce_job(socket_nodo, reduce_dest, nodo_host);
		result = (result > 0) ? receive_answer_reduce(socket_nodo, &answer_reduce) : result;
	} else {
		answer_reduce = INCORRECT_NODO;
	}

	if(result <= 0) {
		answer_reduce = REDUCE_NOT_OK;
	}
	t_buffer* reduce_result_buff;

	switch(answer_reduce) {

		case REDUCE_OK: case REDUCE_NOT_OK:
			reduce_result_buff = buffer_create_with_protocol(answer_reduce);
			if(reduce_dest->prot == ORDER_PARTIAL_REDUCE) {
				buffer_add_int(reduce_result_buff, reduce_dest->id_nodo_host);
			}
			break;

		case INCORRECT_NODO:
			reduce_result_buff = buffer_create_with_protocol(NODO_NOT_FOUND);
			if(reduce_dest->prot == ORDER_REDUCE) {
				buffer_add_int(reduce_result_buff, 1);
				buffer_add_int(reduce_result_buff, nodo_host->id_nodo);
			}
			break;

		case NODO_NOT_FOUND:
			reduce_result_buff = buffer_create_with_protocol(NODO_NOT_FOUND);
			if(reduce_dest->prot == ORDER_REDUCE) {
				uint32_t i,amount_nodes = 0;
				result = receive_int_in_order(socket_nodo, &amount_nodes);
				buffer_add_int(reduce_result_buff, amount_nodes);

				for(i=0;(i<amount_nodes) && (result > 0); i++) {
					uint32_t id_nodo = 0;
					result = receive_int_in_order(socket_nodo, &id_nodo);
					buffer_add_int(reduce_result_buff, id_nodo);
				}

				if(result <= 0) {
					buffer_destroy(reduce_result_buff);
					reduce_result_buff = buffer_create_with_protocol(REDUCE_NOT_OK);
				}
			}
			break;

		case TEMP_NOT_FOUND:
			reduce_result_buff = buffer_create_with_protocol(TEMP_NOT_FOUND);
			uint32_t i,amount_temps = 0;
			result = receive_int_in_order(socket_nodo, &amount_temps);
			buffer_add_int(reduce_result_buff, amount_temps);
			for(i=0;(i<amount_temps) && (result > 0); i++) {
				uint32_t id_nodo = 0;
				result = receive_int_in_order(socket_nodo, &id_nodo);
				if(reduce_dest->prot == ORDER_REDUCE) {
					buffer_add_int(reduce_result_buff, id_nodo);
				}
				char* path = NULL;
				result = (result > 0) ? receive_dinamic_array_in_order(socket_nodo, (void **) &path) : result;
				if(path != NULL) {
					buffer_add_string(reduce_result_buff, path);
					free(path);
				}
			}

			if(result <= 0) {
				buffer_destroy(reduce_result_buff);
				reduce_result_buff = buffer_create_with_protocol(REDUCE_NOT_OK);
			}
			break;

		default:
			reduce_result_buff = buffer_create_with_protocol(REDUCE_NOT_OK);
			break;


	}

	if((socket_nodo != -1) && (result > 0)) {
		close(socket_nodo);
	}

	pthread_mutex_lock(&conex_marta_ready);
	result = (result > 0) ? send_buffer_and_destroy(socket_marta, reduce_result_buff) : result;
	pthread_mutex_unlock(&conex_marta_ready);


	free(ip_nodo);
	free_reduce_nodo_dest(nodo_host);
	free_reduce_dest(reduce_dest);
}
Beispiel #12
0
/*
 * Set a given bbox matching a feature collection's outerboundaries
 * or a simple feature's outerboundaries
 * Bbox is set from a list containing one or several layer names
 * and optionnaly one or several WHERE SQL statement following each layer name
 */
ows_bbox *ows_bbox_boundaries(ows * o, list * from, list * where, ows_srs * srs)
{
    ows_bbox *bb;
    buffer *sql;
    list *geom;
    list_node *ln_from, *ln_where, *ln_geom;
    PGresult *res;

    assert(o);
    assert(from);
    assert(where);
    assert(srs);

    bb = ows_bbox_init();

    if (from->size != where->size) return bb;

    sql = buffer_init();
    /* Put into a buffer the SQL request calculating an extent */
    buffer_add_str(sql, "SELECT ST_xmin(g.extent), ST_ymin(g.extent), ST_xmax(g.extent), ST_ymax(g.extent) FROM ");
    buffer_add_str(sql, "(SELECT ST_Extent(foo.the_geom) as extent FROM ( ");

    /* For each layer name or each geometry column, make an union between retrieved features */
    for (ln_from = from->first, ln_where = where->first; ln_from ; ln_from = ln_from->next, ln_where = ln_where->next) {

        geom = ows_psql_geometry_column(o, ln_from->value);

        for (ln_geom = geom->first ; ln_geom ; ln_geom = ln_geom->next) {
            buffer_add_str(sql, " (SELECT ST_Transform(\"");
            buffer_copy(sql, ln_geom->value);
            buffer_add_str(sql, "\"::geometry, ");
            buffer_add_int(sql, srs->srid);
            buffer_add_str(sql, ") AS \"the_geom\" FROM ");
            buffer_copy(sql, ows_psql_schema_name(o, ln_from->value));
            buffer_add_str(sql, ".\"");
            buffer_copy(sql, ows_psql_table_name(o, ln_from->value));
            buffer_add_str(sql, "\" ");
            buffer_copy(sql, ln_where->value);
            buffer_add_str(sql, ")");

            if (ln_geom->next) buffer_add_str(sql, " UNION ALL ");
        }

        if (ln_from->next) buffer_add_str(sql, " UNION ALL ");
    }

    buffer_add_str(sql, " ) AS foo) AS g");

    res = ows_psql_exec(o, sql->buf);
    buffer_free(sql);

    if (PQresultStatus(res) != PGRES_TUPLES_OK && PQntuples(res) != 4) {
        PQclear(res);
        return bb;
    }

    bb->xmin = strtod(PQgetvalue(res, 0, 0), NULL);
    bb->ymin = strtod(PQgetvalue(res, 0, 1), NULL);
    bb->xmax = strtod(PQgetvalue(res, 0, 2), NULL);
    bb->ymax = strtod(PQgetvalue(res, 0, 3), NULL);
    /* TODO Error handling */

    ows_srs_copy(bb->srs, srs);

    PQclear(res);
    return bb;
}
Beispiel #13
0
/*
 * Specifies the list of feature types available from the wfs
 * Used for both 1.0.0 && 1.1.0 versions
 */
static void wfs_feature_type_list(ows * o)
{
  ows_layer_node *ln;
  ows_geobbox *gb;

  int srid_int;
  buffer *srid;
  buffer *srs;
  list_node *keyword, *l_srid;
  int s;
  bool writable, retrievable;

  assert(o);

  writable = false;
  retrievable = false;

  fprintf(o->output, " <FeatureTypeList>\n");

  /* print global operations */

  if (    ows_layer_list_retrievable(o->layers)
          || ows_layer_list_writable(o->layers))
    fprintf(o->output, "  <Operations>\n");

  if (ows_layer_list_retrievable(o->layers)) {
    if (ows_version_get(o->request->version) == 100)
      fprintf(o->output, "   <Query/>\n");
    else if (ows_version_get(o->request->version) == 110)
      fprintf(o->output, " <Operation>Query</Operation>\n");

    retrievable = true;
  }

  if (ows_layer_list_writable(o->layers)) {
    if (ows_version_get(o->request->version) == 100) {
      fprintf(o->output, "   <Insert/>\n");
      fprintf(o->output, "   <Update/>\n");
      fprintf(o->output, "   <Delete/>\n");
    } else if (ows_version_get(o->request->version) == 110) {
      fprintf(o->output, "   <Operation>Insert</Operation>\n");
      fprintf(o->output, "   <Operation>Update</Operation>\n");
      fprintf(o->output, "   <Operation>Delete</Operation>\n");
    }

    writable = true;
  }

  if (    ows_layer_list_retrievable(o->layers)
          || ows_layer_list_writable(o->layers))
    fprintf(o->output, "  </Operations>\n");

  for (ln = o->layers->first ; ln ; ln = ln->next) {
    /* print each feature type */
    if (ows_layer_match_table(o, ln->layer->name)) {

      fprintf(o->output, "<FeatureType xmlns:%s=\"%s\">\n",
              ln->layer->ns_prefix->buf, ln->layer->ns_uri->buf);

      /* name */
      if (ln->layer->name) {
        for (s = 0; s < ln->layer->depth; s++) fprintf(o->output, " ");

        fprintf(o->output, " <Name>");
        buffer_flush(ows_layer_uri_to_prefix(o->layers, ln->layer->name), o->output);
        fprintf(o->output, "</Name>\n");
      }

      /* title */
      if (ln->layer->title) {
        for (s = 0; s < ln->layer->depth; s++) fprintf(o->output, " ");

        fprintf(o->output, " <Title>");
        buffer_flush(ln->layer->title, o->output);
        fprintf(o->output, "</Title>\n");
      }

      /* abstract */
      if (ln->layer->abstract) {
        for (s = 0; s < ln->layer->depth; s++) fprintf(o->output, " ");

        fprintf(o->output, " <Abstract>");
        buffer_flush(ln->layer->abstract, o->output);
        fprintf(o->output, "</Abstract>\n");
      }

      /* keywords */
      if (ln->layer->keywords) {
        for (s = 0; s < ln->layer->depth; s++) fprintf(o->output, " ");

        fprintf(o->output, " <Keywords>");

        for (keyword = ln->layer->keywords->first ; keyword ; keyword = keyword->next) {
          if (ows_version_get(o->request->version) == 100) {
            fprintf(o->output, "%s", keyword->value->buf);
            if (keyword->next) fprintf(o->output, ",");
          } else if (ows_version_get(o->request->version) == 110) {
            fprintf(o->output, "  <Keyword>");
            fprintf(o->output, "%s", keyword->value->buf);
            fprintf(o->output, "</Keyword>");
          }
        }

        fprintf(o->output, "</Keywords>\n");
      }

      /* SRS */
      srid = buffer_init();
      srid_int = ows_srs_get_srid_from_layer(o, ln->layer->name);
      buffer_add_int(srid, srid_int);
      srs = ows_srs_get_from_a_srid(o, srid_int);

      if (srs->use) {
        if (ows_version_get(o->request->version) == 100) {
          fprintf(o->output, " <SRS>");
          buffer_flush(srs, o->output);
          fprintf(o->output, "</SRS>\n");
        } else if (ows_version_get(o->request->version) == 110) {
          fprintf(o->output, " <DefaultSRS>urn:ogc:def:crs:EPSG::%s</DefaultSRS>\n", srid->buf);

          if (ln->layer->srid) {
            for (l_srid = ln->layer->srid->first; l_srid; l_srid = l_srid->next) {
              if (!buffer_cmp(srid, l_srid->value->buf)) {
                fprintf(o->output, " <OtherSRS>urn:ogc:def:crs:EPSG::%s</OtherSRS>\n", l_srid->value->buf);
              }
            }
          }
        }
      } else {
        if (ows_version_get(o->request->version) == 100)
          fprintf(o->output, " <SRS></SRS>\n");
        else if (ows_version_get(o->request->version) == 110)
          fprintf(o->output, " <NoSRS/>");
      }
      /* Operations */
      if (retrievable != ln->layer->retrievable || writable != ln->layer->writable) {
        fprintf(o->output, "  <Operations>\n");

        if (retrievable == false && ln->layer->retrievable == true) {
          if (ows_version_get(o->request->version) == 100)
            fprintf(o->output, "   <Query/>\n");
          else if (ows_version_get(o->request->version) == 110)
            fprintf(o->output, "   <Operation>Query</Operation>\n");
        }

        if (writable == false && ln->layer->writable == true) {
          if (ows_version_get(o->request->version) == 100) {
            fprintf(o->output, "   <Insert/>\n");
            fprintf(o->output, "   <Update/>\n");
            fprintf(o->output, "   <Delete/>\n");
          } else if (ows_version_get(o->request->version) == 110) {
            fprintf(o->output, "   <Operation>Insert</Operation>\n");
            fprintf(o->output, "   <Operation>Update</Operation>\n");
            fprintf(o->output, "   <Operation>Delete</Operation>\n");
          }
        }

        fprintf(o->output, "  </Operations>\n");
      }

      /* Boundaries */
      if (!ln->layer->geobbox) {
        gb = ows_geobbox_compute(o, ln->layer->name);
      } else {
        gb = ows_geobbox_init();
        gb->west = ln->layer->geobbox->west;
        gb->east = ln->layer->geobbox->east;
        gb->south = ln->layer->geobbox->south;
        gb->north = ln->layer->geobbox->north;
      }
      assert(gb);

      for (s = 0; s < ln->layer->depth; s++) fprintf(o->output, " ");

      if (ows_version_get(o->request->version) == 100)
        fprintf(o->output, " <LatLongBoundingBox");
      else if (ows_version_get(o->request->version) == 110)
        fprintf(o->output, " <ows:WGS84BoundingBox>");

      if (gb->east != DBL_MIN) {
        if (ows_version_get(o->request->version) == 100) {
          if (gb->west < gb->east)
            fprintf(o->output, " minx='%.*f'", o->degree_precision, gb->west);
          else
            fprintf(o->output, " minx='%.*f'", o->degree_precision, gb->east);

          if (gb->north < gb->south)
            fprintf(o->output, " miny='%.*f'", o->degree_precision, gb->north);
          else
            fprintf(o->output, " miny='%.*f'", o->degree_precision, gb->south);

          if (gb->west < gb->east)
            fprintf(o->output, " maxx='%.*f'", o->degree_precision, gb->east);
          else
            fprintf(o->output, " maxx='%.*f'", o->degree_precision, gb->west);

          if (gb->north < gb->south)
            fprintf(o->output, " maxy='%.*f'", o->degree_precision, gb->south);
          else
            fprintf(o->output, " maxy='%.*f'", o->degree_precision, gb->north);

          fprintf(o->output, " />\n");
        } else if (ows_version_get(o->request->version) == 110) {
          fprintf(o->output, " <ows:LowerCorner>%.*f %.*f</ows:LowerCorner>",
                  o->degree_precision, gb->west, o->degree_precision, gb->south);
          fprintf(o->output, " <ows:UpperCorner>%.*f %.*f</ows:UpperCorner>",
                  o->degree_precision, gb->east, o->degree_precision, gb->north);
        }
      } else {
        if (ows_version_get(o->request->version) == 100) {
          fprintf(o->output, " minx='0' miny='0' maxx='0' maxy='0'/>\n");
        } else if (ows_version_get(o->request->version) == 110) {
          fprintf(o->output, " <ows:LowerCorner>0 0</ows:LowerCorner>");
          fprintf(o->output, " <ows:UpperCorner>0 0</ows:UpperCorner>");
        }
      }

      if (ows_version_get(o->request->version) == 110)
        fprintf(o->output, " </ows:WGS84BoundingBox>\n");

      buffer_free(srid);
      buffer_free(srs);
      ows_geobbox_free(gb);

      fprintf(o->output, "</FeatureType>\n");
    }
  }

  fprintf(o->output, " </FeatureTypeList>\n");
}