Files
osm2pgsql/output-pgsql.c
2008-07-24 00:15:03 +00:00

1275 lines
40 KiB
C

/* Implements the mid-layer processing for osm2pgsql
* using several PostgreSQL tables
*
* This layer stores data read in from the planet.osm file
* and is then read by the backend processing code to
* emit the final geometry-enabled output formats
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#ifdef HAVE_PTHREAD
#include <pthread.h>
#endif
#include <libpq-fe.h>
#include "osmtypes.h"
#include "output.h"
#include "reprojection.h"
#include "output-pgsql.h"
#include "build_geometry.h"
#include "middle.h"
#include "pgsql.h"
#define SRID (project_getprojinfo()->srs)
#define MAX_STYLES 100
enum table_id {
t_point, t_line, t_poly, t_roads
};
static const struct output_options *Options;
/* Tables to output */
static struct s_table {
//enum table_id table;
char *name;
const char *type;
PGconn *sql_conn;
char buffer[1024];
unsigned int buflen;
int copyMode;
} tables [] = {
{ name: "%s_point", type: "POINT" },
{ name: "%s_line", type: "LINESTRING"},
{ name: "%s_polygon", type: "POLYGON" },
{ name: "%s_roads", type: "LINESTRING"}
};
#define NUM_TABLES ((signed)(sizeof(tables) / sizeof(tables[0])))
#define FLAG_POLYGON 1 /* For polygon table */
#define FLAG_LINEAR 2 /* For lines table */
#define FLAG_NOCACHE 4 /* Optimisation: don't bother remembering this one */
#define FLAG_DELETE 8 /* These tags should be simply deleted on sight */
static struct flagsname {
char *name;
int flag;
} tagflags[] = {
{ name: "polygon", flag: FLAG_POLYGON },
{ name: "linear", flag: FLAG_LINEAR },
{ name: "nocache", flag: FLAG_NOCACHE },
{ name: "delete", flag: FLAG_DELETE }
};
#define NUM_FLAGS ((signed)(sizeof(tagflags) / sizeof(tagflags[0])))
/* Table columns, representing key= tags */
struct taginfo {
char *name;
char *type;
int flags;
int count;
};
static struct taginfo *exportList[4]; /* Indexed by enum table_id */
static int exportListCount[4];
/* Data to generate z-order column and road table
* The name of the roads table is misleading, this table
* is used for any feature to be shown at low zoom.
* This includes railways and administrative boundaries too
*/
static struct {
int offset;
const char *highway;
int roads;
} layers[] = {
{ 9, "motorway", 1 },
{ 9, "motorway_link", 1 },
{ 8, "trunk", 1 },
{ 8, "trunk_link", 1 },
{ 7, "primary", 1 },
{ 7, "primary_link", 1 },
{ 6, "secondary", 1 },
{ 6, "secondary_link",1 },
// 5 = railway
{ 4, "tertiary", 0 },
{ 4, "tertiary_link", 0 },
{ 3, "residential", 0 },
{ 3, "unclassified", 0 },
{ 3, "road", 0 },
{ 3, "minor", 0 }
};
static const unsigned int nLayers = (sizeof(layers)/sizeof(*layers));
static int pgsql_delete_way_from_output(int osm_id);
static int pgsql_delete_relation_from_output(int osm_id);
static int pgsql_process_relation(int id, struct member *members, int member_count, struct keyval *tags, int exists);
void read_style_file( char *filename )
{
FILE *in;
int lineno = 0;
exportList[OSMTYPE_NODE] = malloc( sizeof(struct taginfo) * MAX_STYLES );
exportList[OSMTYPE_WAY] = malloc( sizeof(struct taginfo) * MAX_STYLES );
in = fopen( filename, "rt" );
if( !in )
{
fprintf( stderr, "Couldn't open style file '%s': %s\n", filename, strerror(errno) );
exit_nicely();
}
char buffer[1024];
while( fgets( buffer, sizeof(buffer), in) != NULL )
{
lineno++;
char osmtype[24];
char tag[24];
char datatype[24];
char flags[128];
int i;
char *str;
str = strchr( buffer, '#' );
if( str )
*str = '\0';
int fields = sscanf( buffer, "%23s %23s %23s %127s", osmtype, tag, datatype, flags );
if( fields <= 0 ) /* Blank line */
continue;
if( fields < 3 )
{
fprintf( stderr, "Error reading style file line %d (fields=%d)\n", lineno, fields );
exit_nicely();
}
struct taginfo temp;
temp.name = strdup(tag);
temp.type = strdup(datatype);
temp.flags = 0;
for( str = strtok( flags, ",\r\n" ); str; str = strtok(NULL, ",\r\n") )
{
for( i=0; i<NUM_FLAGS; i++ )
{
if( strcmp( tagflags[i].name, str ) == 0 )
{
temp.flags |= tagflags[i].flag;
break;
}
}
if( i == NUM_FLAGS )
fprintf( stderr, "Unknown flag '%s' line %d, ignored\n", str, lineno );
}
temp.count = 0;
// printf("%s %s %d %d\n", temp.name, temp.type, temp.polygon, offset );
int flag = 0;
if( strstr( osmtype, "node" ) )
{
memcpy( &exportList[ OSMTYPE_NODE ][ exportListCount[ OSMTYPE_NODE ] ], &temp, sizeof(temp) );
exportListCount[ OSMTYPE_NODE ]++;
flag = 1;
}
if( strstr( osmtype, "way" ) )
{
memcpy( &exportList[ OSMTYPE_WAY ][ exportListCount[ OSMTYPE_WAY ] ], &temp, sizeof(temp) );
exportListCount[ OSMTYPE_WAY ]++;
flag = 1;
}
if( !flag )
{
fprintf( stderr, "Weird style line %d\n", lineno );
exit_nicely();
}
}
fclose(in);
}
static void free_style_refs(const char *name, const char *type)
{
// Find and remove any other references to these pointers
// This would be way easier if we kept a single list of styles
// Currently this scales with n^2 number of styles
int i,j;
for (i=0; i<NUM_TABLES; i++) {
for(j=0; j<exportListCount[i]; j++) {
if (exportList[i][j].name == name)
exportList[i][j].name = NULL;
if (exportList[i][j].type == type)
exportList[i][j].type = NULL;
}
}
}
static void free_style(void)
{
int i, j;
for (i=0; i<NUM_TABLES; i++) {
for(j=0; j<exportListCount[i]; j++) {
free(exportList[i][j].name);
free(exportList[i][j].type);
free_style_refs(exportList[i][j].name, exportList[i][j].type);
}
}
for (i=0; i<NUM_TABLES; i++)
free(exportList[i]);
}
/* Handles copying out, but coalesces the data into large chunks for
* efficiency. PostgreSQL doesn't actually need this, but each time you send
* a block of data you get 5 bytes of overhead. Since we go column by column
* with most empty and one byte delimiters, without this optimisation we
* transfer three times the amount of data necessary.
*/
void copy_to_table(enum table_id table, const char *sql)
{
PGconn *sql_conn = tables[table].sql_conn;
unsigned int len = strlen(sql);
unsigned int buflen = tables[table].buflen;
char *buffer = tables[table].buffer;
/* Return to copy mode if we dropped out */
if( !tables[table].copyMode )
{
pgsql_exec(sql_conn, PGRES_COPY_IN, "COPY %s FROM STDIN", tables[table].name);
tables[table].copyMode = 1;
}
/* If the combination of old and new data is too big, flush old data */
if( (unsigned)(buflen + len) > sizeof( tables[table].buffer )-10 )
{
pgsql_CopyData(tables[table].name, sql_conn, buffer);
buflen = 0;
/* If new data by itself is also too big, output it immediately */
if( (unsigned)len > sizeof( tables[table].buffer )-10 )
{
pgsql_CopyData(tables[table].name, sql_conn, sql);
len = 0;
}
}
/* Normal case, just append to buffer */
if( len > 0 )
{
strcpy( buffer+buflen, sql );
buflen += len;
len = 0;
}
/* If we have completed a line, output it */
if( buflen > 0 && buffer[buflen-1] == '\n' )
{
pgsql_CopyData(tables[table].name, sql_conn, buffer);
buflen = 0;
}
tables[table].buflen = buflen;
}
static int add_z_order_polygon(struct keyval *tags, int *roads)
{
const char *natural = getItem(tags, "natural");
const char *layer = getItem(tags, "layer");
int z_order, l;
char z[13];
/* Discard polygons with the tag natural=coastline */
if (natural && !strcmp(natural, "coastline"))
return 1;
l = layer ? strtol(layer, NULL, 10) : 0;
z_order = 10 * l;
*roads = 0;
snprintf(z, sizeof(z), "%d", z_order);
addItem(tags, "z_order", z, 0);
return 0;
}
static int add_z_order_line(struct keyval *tags, int *roads)
{
const char *layer = getItem(tags, "layer");
const char *highway = getItem(tags, "highway");
const char *bridge = getItem(tags, "bridge");
const char *tunnel = getItem(tags, "tunnel");
const char *railway = getItem(tags, "railway");
const char *boundary= getItem(tags, "boundary");
int z_order = 0;
int l;
unsigned int i;
char z[13];
l = layer ? strtol(layer, NULL, 10) : 0;
z_order = 10 * l;
*roads = 0;
if (highway) {
for (i=0; i<nLayers; i++) {
if (!strcmp(layers[i].highway, highway)) {
z_order += layers[i].offset;
*roads = layers[i].roads;
break;
}
}
}
if (railway && strlen(railway)) {
z_order += 5;
*roads = 1;
}
// Administrative boundaries are rendered at low zooms so we prefer to use the roads table
if (boundary && !strcmp(boundary, "administrative"))
*roads = 1;
if (bridge && (!strcmp(bridge, "true") || !strcmp(bridge, "yes") || !strcmp(bridge, "1")))
z_order += 10;
if (tunnel && (!strcmp(tunnel, "true") || !strcmp(tunnel, "yes") || !strcmp(tunnel, "1")))
z_order -= 10;
snprintf(z, sizeof(z), "%d", z_order);
addItem(tags, "z_order", z, 0);
return 0;
}
static int add_z_order(struct keyval* tags, int polygon, int *roads)
{
return polygon ? add_z_order_polygon(tags, roads) : add_z_order_line(tags, roads);
}
static void fix_motorway_shields(struct keyval *tags)
{
const char *highway = getItem(tags, "highway");
const char *name = getItem(tags, "name");
const char *ref = getItem(tags, "ref");
/* The current mapnik style uses ref= for motorway shields but some roads just have name= */
if (!highway || strcmp(highway, "motorway"))
return;
if (name && !ref)
addItem(tags, "ref", name, 0);
}
/* Append all alternate name:xx on to the name tag with space sepatators.
* name= always comes first, the alternates are in no particular order
* Note: A new line may be better but this does not work with Mapnik
*
* <tag k="name" v="Ben Nevis" />
* <tag k="name:gd" v="Ben Nibheis" />
* becomes:
* <tag k="name" v="Ben Nevis Ben Nibheis" />
*/
void compress_tag_name(struct keyval *tags)
{
const char *name = getItem(tags, "name");
struct keyval *name_ext = getMatches(tags, "name:");
struct keyval *p;
char out[2048];
if (!name_ext)
return;
out[0] = '\0';
if (name) {
strncat(out, name, sizeof(out)-1);
strncat(out, " ", sizeof(out)-1);
}
while((p = popItem(name_ext)) != NULL) {
/* Exclude name:source = "dicataphone" and duplicates */
if (strcmp(p->key, "name:source") && !strstr(out, p->value)) {
strncat(out, p->value, sizeof(out)-1);
strncat(out, " ", sizeof(out)-1);
}
freeItem(p);
}
free(name_ext);
// Remove trailing space
out[strlen(out)-1] = '\0';
//fprintf(stderr, "*** New name: %s\n", out);
updateItem(tags, "name", out);
}
static void pgsql_out_cleanup(void)
{
int i;
for (i=0; i<NUM_TABLES; i++) {
if (tables[i].sql_conn) {
PQfinish(tables[i].sql_conn);
tables[i].sql_conn = NULL;
}
}
}
/* Escape data appropriate to the type */
static void escape_type(char *sql, int len, const char *value, const char *type) {
int items, from, to;
if ( !strcmp(type, "int4") ) {
/* For integers we take the first number, or the average if it's a-b */
items = sscanf(value, "%d-%d", &from, &to);
if ( items == 1 ) {
sprintf(sql, "%d", from);
} else if ( items == 2 ) {
sprintf(sql, "%d", (from + to) / 2);
} else {
sprintf(sql, "\\N");
}
} else {
escape(sql, len, value);
}
}
/* example from: pg_dump -F p -t planet_osm gis
COPY planet_osm (osm_id, name, place, landuse, leisure, "natural", man_made, waterway, highway, railway, amenity, tourism, learning, building, bridge, layer, way) FROM stdin;
17959841 \N \N \N \N \N \N \N bus_stop \N \N \N \N \N \N -\N 0101000020E610000030CCA462B6C3D4BF92998C9B38E04940
17401934 The Horn \N \N \N \N \N \N \N \N pub \N \N \N \N -\N 0101000020E6100000C12FC937140FD5BFB4D2F4FB0CE04940
...
mine - 01 01000000 48424298424242424242424256427364
psql - 01 01000020 E6100000 30CCA462B6C3D4BF92998C9B38E04940
01 01000020 E6100000 48424298424242424242424256427364
0x2000_0000 = hasSRID, following 4 bytes = srid, not supported by geos WKBWriter
Workaround - output SRID=4326;<WKB>
*/
static int pgsql_out_node(int id, struct keyval *tags, double node_lat, double node_lon)
{
char sql[2048], *v;
int i;
sprintf(sql, "%d\t", id);
copy_to_table(t_point, sql);
for (i=0; i < exportListCount[OSMTYPE_NODE]; i++) {
if( exportList[OSMTYPE_NODE][i].flags & FLAG_DELETE )
continue;
if ((v = getItem(tags, exportList[OSMTYPE_NODE][i].name)))
{
escape_type(sql, sizeof(sql), v, exportList[OSMTYPE_NODE][i].type);
exportList[OSMTYPE_NODE][i].count++;
}
else
sprintf(sql, "\\N");
copy_to_table(t_point, sql);
copy_to_table(t_point, "\t");
}
sprintf(sql, "SRID=%d;POINT(%.15g %.15g)", SRID, node_lon, node_lat);
copy_to_table(t_point, sql);
copy_to_table(t_point, "\n");
return 0;
}
static void write_wkts(int id, struct keyval *tags, const char *wkt, enum table_id table)
{
int j;
char sql[2048];
const char*v;
sprintf(sql, "%d\t", id);
copy_to_table(table, sql);
for (j=0; j < exportListCount[OSMTYPE_WAY]; j++) {
if( exportList[OSMTYPE_WAY][j].flags & FLAG_DELETE )
continue;
if ((v = getItem(tags, exportList[OSMTYPE_WAY][j].name)))
{
exportList[OSMTYPE_WAY][j].count++;
escape_type(sql, sizeof(sql), v, exportList[OSMTYPE_WAY][j].type);
}
else
sprintf(sql, "\\N");
copy_to_table(table, sql);
copy_to_table(table, "\t");
}
sprintf(sql, "SRID=%d;", SRID);
copy_to_table(table, sql);
copy_to_table(table, wkt);
copy_to_table(table, "\n");
}
void add_parking_node(int id, struct keyval *tags, double node_lat, double node_lon)
{
// insert into planet_osm_point(osm_id,name,amenity,way) select osm_id,name,amenity,centroid(way) from planet_osm_polygon where amenity='parking';
const char *access = getItem(tags, "access");
const char *amenity = getItem(tags, "amenity");
const char *name = getItem(tags, "name");
struct keyval nodeTags;
if (!amenity || strcmp(amenity, "parking"))
return;
// Do not add a 'P' symbol if access is defined and something other than public.
if (access && strcmp(access, "public"))
return;
initList(&nodeTags);
addItem(&nodeTags, "amenity", amenity, 0);
if (name)
addItem(&nodeTags, "name", name, 0);
if (access)
addItem(&nodeTags, "access", access, 0);
pgsql_out_node(id, &nodeTags, node_lat, node_lon);
resetList(&nodeTags);
}
static int tag_indicates_polygon(enum OsmType type, const char *key)
{
int i;
for (i=0; i < exportListCount[type]; i++) {
if( strcmp( exportList[type][i].name, key ) == 0 )
return exportList[type][i].flags & FLAG_POLYGON;
}
return 0;
}
/* Go through the given tags and determine the union of flags. Also remove
* any tags from the list that we don't know about */
unsigned int pgsql_filter_tags(enum OsmType type, struct keyval *tags, int *polygon)
{
int i, filter = 1;
int flags = 0;
const char *area;
struct keyval *item;
struct keyval temp;
initList(&temp);
/* We used to only go far enough to determine if it's a polygon or not, but now we go through and filter stuff we don't need */
while( (item = popItem(tags)) != NULL )
{
for (i=0; i < exportListCount[type]; i++)
{
if( strcmp( exportList[type][i].name, item->key ) == 0 )
{
if( exportList[type][i].flags & FLAG_DELETE )
{
freeItem( item );
item = NULL;
break;
}
filter = 0;
flags |= exportList[type][i].flags;
pushItem( &temp, item );
item = NULL;
break;
}
}
if( i == exportListCount[type] )
{
freeItem( item );
item = NULL;
}
}
/* Move from temp list back to original list */
while( (item = popItem(&temp)) != NULL )
pushItem( tags, item );
*polygon = flags & FLAG_POLYGON;
/* Special case allowing area= to override anything else */
if ((area = getItem(tags, "area"))) {
if (!strcmp(area, "yes") || !strcmp(area, "true") ||!strcmp(area, "1"))
*polygon = 1;
else if (!strcmp(area, "no") || !strcmp(area, "false") || !strcmp(area, "0"))
*polygon = 0;
}
return filter;
}
/*
COPY planet_osm (osm_id, name, place, landuse, leisure, "natural", man_made, waterway, highway, railway, amenity, tourism, learning, bu
ilding, bridge, layer, way) FROM stdin;
198497 Bedford Road \N \N \N \N \N \N residential \N \N \N \N \N \N \N 0102000020E610000004000000452BF702B342D5BF1C60E63BF8DF49406B9C4D470037D5BF5471E316F3DF4940DFA815A6EF35D5BF9AE95E27F5DF4940B41EB
E4C1421D5BF24D06053E7DF4940
212696 Oswald Road \N \N \N \N \N \N minor \N \N \N \N \N \N \N 0102000020E610000004000000467D923B6C22D5BFA359D93EE4DF4940B3976DA7AD11D5BF84BBB376DBDF4940997FF44D9A06D5BF4223D8B8FEDF49404D158C4AEA04D
5BF5BB39597FCDF4940
*/
static int pgsql_out_way(int id, struct keyval *tags, struct osmNode *nodes, int count, int exists)
{
int polygon = 0, roads = 0;
char *wkt;
double area, interior_lat, interior_lon;
/* If the flag says this object may exist already, delete it first */
if(exists)
pgsql_delete_way_from_output(id);
if (pgsql_filter_tags(OSMTYPE_WAY, tags, &polygon) || add_z_order(tags, polygon, &roads))
return 0;
//compress_tag_name(tags);
fix_motorway_shields(tags);
wkt = get_wkt_simple(nodes, count, polygon, &area, &interior_lon, &interior_lat);
if (wkt && strlen(wkt)) {
/* FIXME: there should be a better way to detect polygons */
if (!strncmp(wkt, "POLYGON", strlen("POLYGON"))) {
if (area > 0.0) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%f", area);
addItem(tags, "way_area", tmp, 0);
}
write_wkts(id, tags, wkt, t_poly);
add_parking_node(id, tags, interior_lat, interior_lon);
} else {
write_wkts(id, tags, wkt, t_line);
if (roads)
write_wkts(id, tags, wkt, t_roads);
}
}
free(wkt);
return 0;
}
static int pgsql_out_relation(int id, struct keyval *rel_tags, struct osmNode **xnodes, struct keyval *xtags, int *xcount, int *xid, const char **xrole)
{
int i, wkt_size;
double interior_lat, interior_lon;
int polygon = 0, roads = 0;
int make_polygon = 0;
struct keyval tags, *p, poly_tags;
char *type;
#if 0
fprintf(stderr, "Got relation with counts:");
for (i=0; xcount[i]; i++)
fprintf(stderr, " %d", xcount[i]);
fprintf(stderr, "\n");
#endif
/* Get the type, if there's no type we don't care */
type = getItem(rel_tags, "type");
if( !type )
return 0;
initList(&tags);
initList(&poly_tags);
p = rel_tags->next;
while (p != rel_tags) {
addItem(&tags, p->key, p->value, 1);
p = p->next;
}
if( strcmp(type, "route") == 0 )
{
make_polygon = 0;
char *state = getItem(rel_tags, "state");
if (state == NULL) {
state = "";
}
int networknr = -1;
if (getItem(rel_tags, "network") != NULL) {
char *netw = getItem(rel_tags, "network");
if (strcmp(netw, "lcn") == 0) {
networknr = 10;
if (strcmp(state, "alternate") == 0) {
addItem(&tags, "lcn", "alternate", 1);
} else if (strcmp(state, "connection") == 0) {
addItem(&tags, "lcn", "connection", 1);
} else {
addItem(&tags, "lcn", "yes", 1);
}
} else if (strcmp(netw, "rcn") == 0) {
networknr = 11;
if (strcmp(state, "alternate") == 0) {
addItem(&tags, "rcn", "alternate", 1);
} else if (strcmp(state, "connection") == 0) {
addItem(&tags, "rcn", "connection", 1);
} else {
addItem(&tags, "rcn", "yes", 1);
}
} else if (strcmp(netw, "ncn") == 0) {
networknr = 12;
if (strcmp(state, "alternate") == 0) {
addItem(&tags, "ncn", "alternate", 1);
} else if (strcmp(state, "connection") == 0) {
addItem(&tags, "ncn", "connection", 1);
} else {
addItem(&tags, "ncn", "yes", 1);
}
} else if (strcmp(netw, "lwn") == 0) {
networknr = 20;
if (strcmp(state, "alternate") == 0) {
addItem(&tags, "lwn", "alternate", 1);
} else if (strcmp(state, "connection") == 0) {
addItem(&tags, "lwn", "connection", 1);
} else {
addItem(&tags, "lwn", "yes", 1);
}
} else if (strcmp(netw, "rwn") == 0) {
networknr = 21;
if (strcmp(state, "alternate") == 0) {
addItem(&tags, "rwn", "alternate", 1);
} else if (strcmp(state, "connection") == 0) {
addItem(&tags, "rwn", "connection", 1);
} else {
addItem(&tags, "rwn", "yes", 1);
}
} else if (strcmp(netw, "nwn") == 0) {
networknr = 22;
if (strcmp(state, "alternate") == 0) {
addItem(&tags, "nwn", "alternate", 1);
} else if (strcmp(state, "connection") == 0) {
addItem(&tags, "nwn", "connection", 1);
} else {
addItem(&tags, "nwn", "yes", 1);
}
}
}
if (getItem(rel_tags, "preferred_color") != NULL) {
char *a = getItem(rel_tags, "preferred_color");
if (strcmp(a, "0") == 0 || strcmp(a, "1") == 0 || strcmp(a, "2") == 0 || strcmp(a, "3") == 0 || strcmp(a, "4") == 0) {
addItem(&tags, "route_pref_color", a, 1);
} else {
addItem(&tags, "route_pref_color", "0", 1);
}
} else {
addItem(&tags, "route_pref_color", "0", 1);
}
if (getItem(rel_tags, "name") != NULL) {
addItem(&tags, "route_name", getItem(rel_tags, "name"), 1);
}
if (getItem(rel_tags, "ref") != NULL) {
if (networknr == 10) {
addItem(&tags, "lcn_ref", getItem(rel_tags, "ref"), 1);
} else if (networknr == 11) {
addItem(&tags, "rcn_ref", getItem(rel_tags, "ref"), 1);
} else if (networknr == 12) {
addItem(&tags, "ncn_ref", getItem(rel_tags, "ref"), 1);
} else if (networknr == 20) {
addItem(&tags, "lwn_ref", getItem(rel_tags, "ref"), 1);
} else if (networknr == 21) {
addItem(&tags, "rwn_ref", getItem(rel_tags, "ref"), 1);
} else if (networknr == 22) {
addItem(&tags, "nwn_ref", getItem(rel_tags, "ref"), 1);
}
}
}
else if( strcmp( type, "multipolygon" ) == 0 )
{
make_polygon = 1;
/* For multipolygons we add the tags on any non-inner rings */
for (i=0; xcount[i]; i++) {
if (xrole[i] && !strcmp(xrole[i], "inner"))
continue;
p = xtags[i].next;
while (p != &(xtags[i])) {
addItem(&tags, p->key, p->value, 1);
// Collect a list of polygon-like tags, these are later used to
// identify if an inner rings looks like it should be rendered seperately
if (tag_indicates_polygon(OSMTYPE_WAY, p->key)) {
addItem(&poly_tags, p->key, p->value, 1);
//fprintf(stderr, "found a polygon tag: %s=%s\n", p->key, p->value);
}
p = p->next;
}
}
}
else
{
/* Unknown type, just exit */
resetList(&tags);
resetList(&poly_tags);
return 0;
}
if (pgsql_filter_tags(OSMTYPE_WAY, &tags, &polygon) || add_z_order(&tags, polygon, &roads)) {
resetList(&tags);
resetList(&poly_tags);
return 0;
}
wkt_size = build_geometry(id, xnodes, xcount, make_polygon);
if (!wkt_size) {
resetList(&tags);
resetList(&poly_tags);
return 0;
}
for (i=0;i<wkt_size;i++)
{
char *wkt = get_wkt(i);
if (strlen(wkt)) {
/* FIXME: there should be a better way to detect polygons */
if (!strncmp(wkt, "POLYGON", strlen("POLYGON"))) {
double area = get_area(i);
if (area > 0.0) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%f", area);
addItem(&tags, "way_area", tmp, 0);
}
write_wkts(-id, &tags, wkt, t_poly);
get_interior(i, &interior_lat, &interior_lon);
add_parking_node(-id, &tags, interior_lat, interior_lon);
} else {
write_wkts(-id, &tags, wkt, t_line);
if (roads)
write_wkts(-id, &tags, wkt, t_roads);
}
}
free(wkt);
}
clear_wkts();
// If we are creating a multipolygon then we
// mark each member so that we can skip them during iterate_ways
// but only if the polygon-tags look the same as the outer ring
if (make_polygon) {
for (i=0; xcount[i]; i++) {
int match = 1;
struct keyval *p = poly_tags.next;
while (p != &poly_tags) {
const char *v = getItem(&xtags[i], p->key);
//fprintf(stderr, "compare polygon tag: %s=%s vs %s\n", p->key, p->value, v ? v : "null");
if (!v || strcmp(v, p->value)) {
match = 0;
break;
}
p = p->next;
}
if (match) {
//fprintf(stderr, "match for %d\n", xid[i]);
Options->mid->ways_done(xid[i]);
}
}
}
resetList(&tags);
resetList(&poly_tags);
return 0;
}
static int pgsql_out_start(const struct output_options *options)
{
char sql[1024], tmp[128];
PGresult *res;
int i,j;
Options = options;
read_style_file( "default.style" );
for (i=0; i<NUM_TABLES; i++) {
PGconn *sql_conn;
/* Substitute prefix into name of table */
{
char *temp = malloc( strlen(options->prefix) + strlen(tables[i].name) + 1 );
sprintf( temp, tables[i].name, options->prefix );
tables[i].name = temp;
}
fprintf(stderr, "Setting up table: %s\n", tables[i].name);
sql_conn = PQconnectdb(options->conninfo);
/* Check to see that the backend connection was successfully made */
if (PQstatus(sql_conn) != CONNECTION_OK) {
fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(sql_conn));
exit_nicely();
}
tables[i].sql_conn = sql_conn;
if (!options->append) {
sprintf( sql, "DROP TABLE %s;", tables[i].name);
res = PQexec(sql_conn, sql);
PQclear(res); /* Will be an error if table does not exist */
}
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "BEGIN");
enum OsmType type = (i == t_point)?OSMTYPE_NODE:OSMTYPE_WAY;
int numTags = exportListCount[type];
struct taginfo *exportTags = exportList[type];
if (!options->append) {
sprintf(sql, "CREATE TABLE %s ( osm_id int4", tables[i].name );
for (j=0; j < numTags; j++) {
if( exportTags[j].flags & FLAG_DELETE )
continue;
sprintf(tmp, ",\"%s\" %s", exportTags[j].name, exportTags[j].type);
strcat(sql, tmp);
}
strcat(sql, " );\n");
pgsql_exec(sql_conn, PGRES_COMMAND_OK, sql);
pgsql_exec(sql_conn, PGRES_TUPLES_OK, "SELECT AddGeometryColumn('%s', 'way', %d, '%s', 2 );\n",
tables[i].name, SRID, tables[i].type );
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "ALTER TABLE %s ALTER COLUMN way SET NOT NULL;\n", tables[i].name);
/* slim mode needs this to be able to apply diffs */
if( Options->slim )
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "CREATE INDEX %s_pkey ON %s USING BTREE (osm_id);\n", tables[i].name, tables[i].name);
}
pgsql_exec(sql_conn, PGRES_COPY_IN, "COPY %s FROM STDIN", tables[i].name);
tables[i].copyMode = 1;
}
options->mid->start(options);
return 0;
}
static void pgsql_pause_copy(struct s_table *table)
{
PGresult *res;
if( !table->copyMode )
return;
/* Terminate any pending COPY */
int stop = PQputCopyEnd(table->sql_conn, NULL);
if (stop != 1) {
fprintf(stderr, "COPY_END for %s failed: %s\n", table->name, PQerrorMessage(table->sql_conn));
exit_nicely();
}
res = PQgetResult(table->sql_conn);
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "COPY_END for %s failed: %s\n", table->name, PQerrorMessage(table->sql_conn));
PQclear(res);
exit_nicely();
}
PQclear(res);
table->copyMode = 0;
}
static void *pgsql_out_stop_one(void *arg)
{
struct s_table *table = arg;
PGconn *sql_conn = table->sql_conn;
if( table->buflen != 0 )
{
fprintf( stderr, "Internal error: Buffer for %s has %d bytes after end copy", table->name, table->buflen );
exit_nicely();
}
pgsql_pause_copy(table);
// Commit transaction
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "COMMIT");
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "ANALYZE %s;\n", table->name);
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "CREATE TABLE %s_tmp AS SELECT * FROM %s ORDER BY way;\n", table->name, table->name);
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "DROP TABLE %s;\n", table->name);
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "ALTER TABLE %s_tmp RENAME TO %s;\n", table->name, table->name);
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "CREATE INDEX %s_index ON %s USING GIST (way GIST_GEOMETRY_OPS);\n", table->name, table->name);
/* slim mode needs this to be able to apply diffs */
if( Options->slim )
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "CREATE INDEX %s_pkey ON %s USING BTREE (osm_id);\n", table->name, table->name);
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "GRANT SELECT ON %s TO PUBLIC;\n", table->name);
pgsql_exec(sql_conn, PGRES_COMMAND_OK, "ANALYZE %s;\n", table->name);
free(table->name);
return NULL;
}
static void pgsql_out_stop()
{
int i;
#ifdef HAVE_PTHREAD
pthread_t threads[NUM_TABLES];
#endif
/* Processing any remaing to be processed ways */
Options->mid->iterate_ways( pgsql_out_way );
Options->mid->iterate_relations( pgsql_process_relation );
/* No longer need to access middle layer -- release memory */
Options->mid->stop();
#ifdef HAVE_PTHREAD
for (i=0; i<NUM_TABLES; i++) {
int ret = pthread_create(&threads[i], NULL, pgsql_out_stop_one, &tables[i]);
if (ret) {
fprintf(stderr, "pthread_create() returned an error (%d)", ret);
exit_nicely();
}
}
for (i=0; i<NUM_TABLES; i++) {
int ret = pthread_join(threads[i], NULL);
if (ret) {
fprintf(stderr, "pthread_join() returned an error (%d)", ret);
exit_nicely();
}
}
#else
for (i=0; i<NUM_TABLES; i++)
pgsql_out_stop_one(&tables[i]);
#endif
pgsql_out_cleanup();
free_style();
}
static int pgsql_add_node(int id, double lat, double lon, struct keyval *tags)
{
int polygon;
int filter = pgsql_filter_tags(OSMTYPE_NODE, tags, &polygon);
Options->mid->nodes_set(id, lat, lon, tags);
if( !filter )
pgsql_out_node(id, tags, lat, lon);
return 0;
}
static int pgsql_add_way(int id, int *nds, int nd_count, struct keyval *tags)
{
int polygon = 0;
// Check whether the way is: (1) Exportable, (2) Maybe a polygon
int filter = pgsql_filter_tags(OSMTYPE_WAY, tags, &polygon);
// Memory saving hack:-
// If we're not in slim mode and it's not wanted, we can quit right away */
if( !Options->slim && filter )
return 1;
// If this isn't a polygon then it can not be part of a multipolygon
// Hence only polygons are "pending"
Options->mid->ways_set(id, nds, nd_count, tags, (!filter && polygon) ? 1 : 0);
if( !polygon && !filter )
{
/* Get actual node data and generate output */
struct osmNode *nodes = malloc( sizeof(struct osmNode) * nd_count );
int count = Options->mid->nodes_get_list( nodes, nds, nd_count );
pgsql_out_way(id, tags, nodes, count, 0);
free(nodes);
}
return 0;
}
/* This is the workhorse of pgsql_add_relation, split out because it is used as the callback for iterate relations */
static int pgsql_process_relation(int id, struct member *members, int member_count, struct keyval *tags, int exists)
{
// (int id, struct keyval *rel_tags, struct osmNode **xnodes, struct keyval **xtags, int *xcount)
int i, count;
int *xid = malloc( (member_count+1) * sizeof(int) );
const char **xrole = malloc( (member_count+1) * sizeof(const char *) );
int *xcount = malloc( (member_count+1) * sizeof(int) );
struct keyval *xtags = malloc( (member_count+1) * sizeof(struct keyval) );
struct osmNode **xnodes = malloc( (member_count+1) * sizeof(struct osmNode*) );
/* If the flag says this object may exist already, delete it first */
if(exists)
pgsql_delete_relation_from_output(id);
count = 0;
for( i=0; i<member_count; i++ )
{
/* Need to handle more than just ways... */
if( members[i].type != OSMTYPE_WAY )
continue;
initList(&(xtags[count]));
if( Options->mid->ways_get( members[i].id, &(xtags[count]), &(xnodes[count]), &(xcount[count]) ) )
continue;
xid[count] = members[i].id;
xrole[count] = members[i].role;
count++;
}
xnodes[count] = NULL;
xcount[count] = 0;
xid[count] = 0;
xrole[count] = NULL;
// At some point we might want to consider storing the retreived data in the members, rather than as seperate arrays
pgsql_out_relation(id, tags, xnodes, xtags, xcount, xid, xrole);
for( i=0; i<count; i++ )
{
resetList( &(xtags[i]) );
free( xnodes[i] );
}
free(xid);
free(xrole);
free(xcount);
free(xtags);
free(xnodes);
return 0;
}
static int pgsql_add_relation(int id, struct member *members, int member_count, struct keyval *tags)
{
const char *type = getItem(tags, "type");
// Must have a type field or we ignore it
if (!type)
return 0;
/* In slim mode we remember these*/
if(Options->mid->relations_set)
Options->mid->relations_set(id, members, member_count, tags);
// (int id, struct keyval *rel_tags, struct osmNode **xnodes, struct keyval **xtags, int *xcount)
return pgsql_process_relation(id, members, member_count, tags, 0);
}
#define __unused __attribute__ ((unused))
/* Delete is easy, just remove all traces of this object. We don't need to
* worry about finding objects that depend on it, since the same diff must
* contain the change for that also. */
static int pgsql_delete_node(int osm_id)
{
if( !Options->slim )
{
fprintf( stderr, "Cannot apply diffs unless in slim mode\n" );
exit_nicely();
}
pgsql_pause_copy(&tables[t_point]);
pgsql_exec(tables[t_point].sql_conn, PGRES_COMMAND_OK, "DELETE FROM %s WHERE osm_id = %d", tables[t_point].name, osm_id );
Options->mid->nodes_delete(osm_id);
return 0;
}
/* Seperated out because we use it elsewhere */
static int pgsql_delete_way_from_output(int osm_id)
{
/* Optimisation: we only need this is slim mode */
if( !Options->slim )
return 0;
pgsql_pause_copy(&tables[t_roads]);
pgsql_pause_copy(&tables[t_line]);
pgsql_pause_copy(&tables[t_poly]);
pgsql_exec(tables[t_roads].sql_conn, PGRES_COMMAND_OK, "DELETE FROM %s WHERE osm_id = %d", tables[t_roads].name, osm_id );
pgsql_exec(tables[t_line].sql_conn, PGRES_COMMAND_OK, "DELETE FROM %s WHERE osm_id = %d", tables[t_line].name, osm_id );
pgsql_exec(tables[t_poly].sql_conn, PGRES_COMMAND_OK, "DELETE FROM %s WHERE osm_id = %d", tables[t_poly].name, osm_id );
return 0;
}
static int pgsql_delete_way(int osm_id)
{
if( !Options->slim )
{
fprintf( stderr, "Cannot apply diffs unless in slim mode\n" );
exit_nicely();
}
pgsql_delete_way_from_output(osm_id);
Options->mid->ways_delete(osm_id);
return 0;
}
/* Relations are identified by using negative IDs */
static int pgsql_delete_relation_from_output(int osm_id)
{
pgsql_pause_copy(&tables[t_roads]);
pgsql_pause_copy(&tables[t_line]);
pgsql_pause_copy(&tables[t_poly]);
pgsql_exec(tables[t_roads].sql_conn, PGRES_COMMAND_OK, "DELETE FROM %s WHERE osm_id = %d", tables[t_roads].name, -osm_id );
pgsql_exec(tables[t_line].sql_conn, PGRES_COMMAND_OK, "DELETE FROM %s WHERE osm_id = %d", tables[t_line].name, -osm_id );
pgsql_exec(tables[t_poly].sql_conn, PGRES_COMMAND_OK, "DELETE FROM %s WHERE osm_id = %d", tables[t_poly].name, -osm_id );
return 0;
}
static int pgsql_delete_relation(int osm_id)
{
if( !Options->slim )
{
fprintf( stderr, "Cannot apply diffs unless in slim mode\n" );
exit_nicely();
}
pgsql_delete_relation_from_output(osm_id);
Options->mid->relations_delete(osm_id);
return 0;
}
/* Modify is slightly trickier. The basic idea is we simply delete the
* object and create it with the new parameters. Then we need to mark the
* objects that depend on this one */
static int pgsql_modify_node(int osm_id, double lat, double lon, struct keyval *tags)
{
if( !Options->slim )
{
fprintf( stderr, "Cannot apply diffs unless in slim mode\n" );
exit_nicely();
}
pgsql_delete_node(osm_id);
pgsql_add_node(osm_id, lat, lon, tags);
Options->mid->node_changed(osm_id);
return 0;
}
static int pgsql_modify_way(int osm_id, int *nodes, int node_count, struct keyval *tags)
{
if( !Options->slim )
{
fprintf( stderr, "Cannot apply diffs unless in slim mode\n" );
exit_nicely();
}
pgsql_delete_way(osm_id);
pgsql_add_way(osm_id, nodes, node_count, tags);
Options->mid->way_changed(osm_id);
return 0;
}
static int pgsql_modify_relation(int osm_id, struct member *members, int member_count, struct keyval *tags)
{
if( !Options->slim )
{
fprintf( stderr, "Cannot apply diffs unless in slim mode\n" );
exit_nicely();
}
pgsql_delete_relation(osm_id);
pgsql_add_relation(osm_id, members, member_count, tags);
Options->mid->relation_changed(osm_id);
return 0;
}
struct output_t out_pgsql = {
start: pgsql_out_start,
stop: pgsql_out_stop,
cleanup: pgsql_out_cleanup,
node_add: pgsql_add_node,
way_add: pgsql_add_way,
relation_add: pgsql_add_relation,
node_modify: pgsql_modify_node,
way_modify: pgsql_modify_way,
relation_modify: pgsql_modify_relation,
node_delete: pgsql_delete_node,
way_delete: pgsql_delete_way,
relation_delete: pgsql_delete_relation
};