/* */ #include #include #include #include #include #include #include #include #include #include "nominatim.h" #include "export.h" #include "postgresql.h" extern int verbose; int mode = 0; void nominatim_export(int rank_min, int rank_max, const char *conninfo, const char *structuredoutputfile) { xmlTextWriterPtr writer; int rankTotalDone; PGconn *conn; PGresult * res; PGresult * resSectors; PGresult * resPlaces; int rank; int i; int iSector; int tuples; const char *paramValues[2]; int paramLengths[2]; int paramFormats[2]; uint32_t paramRank; uint32_t paramSector; uint32_t sector; Oid pg_prepare_params[2]; conn = PQconnectdb(conninfo); if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn)); exit(EXIT_FAILURE); } pg_prepare_params[0] = PG_OID_INT4; res = PQprepare(conn, "index_sectors", "select geometry_sector,count(*) from placex where rank_search = $1 and indexed_status = 0 group by geometry_sector order by geometry_sector", 1, pg_prepare_params); if (PQresultStatus(res) != PGRES_COMMAND_OK) exit(EXIT_FAILURE); PQclear(res); pg_prepare_params[0] = PG_OID_INT4; pg_prepare_params[1] = PG_OID_INT4; res = PQprepare(conn, "index_sector_places", "select place_id from placex where rank_search = $1 and geometry_sector = $2", 2, pg_prepare_params); if (PQresultStatus(res) != PGRES_COMMAND_OK) exit(EXIT_FAILURE); PQclear(res); nominatim_exportCreatePreparedQueries(conn); // Create the output file writer = nominatim_exportXMLStart(structuredoutputfile); for (rank = rank_min; rank <= rank_max; rank++) { printf("Starting rank %d\n", rank); paramRank = PGint32(rank); paramValues[0] = (char *)¶mRank; paramLengths[0] = sizeof(paramRank); paramFormats[0] = 1; resSectors = PQexecPrepared(conn, "index_sectors", 1, paramValues, paramLengths, paramFormats, 1); if (PQresultStatus(resSectors) != PGRES_TUPLES_OK) { fprintf(stderr, "index_sectors: SELECT failed: %s", PQerrorMessage(conn)); PQclear(resSectors); exit(EXIT_FAILURE); } if (PQftype(resSectors, 0) != PG_OID_INT4) { fprintf(stderr, "Sector value has unexpected type\n"); PQclear(resSectors); exit(EXIT_FAILURE); } if (PQftype(resSectors, 1) != PG_OID_INT8) { fprintf(stderr, "Sector value has unexpected type\n"); PQclear(resSectors); exit(EXIT_FAILURE); } rankTotalDone = 0; for (iSector = 0; iSector < PQntuples(resSectors); iSector++) { sector = PGint32(*((uint32_t *)PQgetvalue(resSectors, iSector, 0))); // Get all the place_id's for this sector paramRank = PGint32(rank); paramValues[0] = (char *)¶mRank; paramLengths[0] = sizeof(paramRank); paramFormats[0] = 1; paramSector = PGint32(sector); paramValues[1] = (char *)¶mSector; paramLengths[1] = sizeof(paramSector); paramFormats[1] = 1; resPlaces = PQexecPrepared(conn, "index_sector_places", 2, paramValues, paramLengths, paramFormats, 1); if (PQresultStatus(resPlaces) != PGRES_TUPLES_OK) { fprintf(stderr, "index_sector_places: SELECT failed: %s", PQerrorMessage(conn)); PQclear(resPlaces); exit(EXIT_FAILURE); } if (PQftype(resPlaces, 0) != PG_OID_INT8) { fprintf(stderr, "Place_id value has unexpected type\n"); PQclear(resPlaces); exit(EXIT_FAILURE); } tuples = PQntuples(resPlaces); for (i = 0; i < tuples; i++) { nominatim_exportPlace(PGint64(*((uint64_t *)PQgetvalue(resPlaces, i, 0))), conn, writer, NULL, NULL); rankTotalDone++; if (rankTotalDone%1000 == 0) printf("Done %i (k)\n", rankTotalDone/1000); } PQclear(resPlaces); } PQclear(resSectors); } nominatim_exportXMLEnd(writer); PQfinish(conn); } void nominatim_exportCreatePreparedQueries(PGconn * conn) { Oid pg_prepare_params[2]; PGresult * res; pg_prepare_params[0] = PG_OID_INT8; res = PQprepare(conn, "placex_details", "select placex.osm_type, placex.osm_id, placex.class, placex.type, placex.name, placex.housenumber, placex.country_code, ST_AsText(placex.geometry), placex.admin_level, placex.rank_address, placex.rank_search, placex.parent_place_id, parent.osm_type, parent.osm_id, placex.indexed_status from placex left outer join placex as parent on (placex.parent_place_id = parent.place_id) where placex.place_id = $1", 1, pg_prepare_params); if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "Error preparing placex_details: %s", PQerrorMessage(conn)); exit(EXIT_FAILURE); } PQclear(res); pg_prepare_params[0] = PG_OID_INT8; res = PQprepare(conn, "placex_address", "select osm_type,osm_id,class,type,distance,cached_rank_address,isaddress from place_addressline join placex on (address_place_id = placex.place_id) where place_addressline.place_id = $1 and address_place_id != place_addressline.place_id order by cached_rank_address asc,osm_type,osm_id", 1, pg_prepare_params); if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "Error preparing placex_address: %s", PQerrorMessage(conn)); exit(EXIT_FAILURE); } PQclear(res); pg_prepare_params[0] = PG_OID_INT8; res = PQprepare(conn, "placex_names", "select (each(name)).key,(each(name)).value from (select name from placex where place_id = $1) as x order by (each(name)).key", 1, pg_prepare_params); if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "Error preparing placex_names: %s", PQerrorMessage(conn)); exit(EXIT_FAILURE); } PQclear(res); pg_prepare_params[0] = PG_OID_INT8; res = PQprepare(conn, "placex_extratags", "select (each(extratags)).key,(each(extratags)).value from (select extratags from placex where place_id = $1) as x order by (each(extratags)).key", 1, pg_prepare_params); if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "Error preparing placex_extratags: %s", PQerrorMessage(conn)); exit(EXIT_FAILURE); } PQclear(res); } xmlTextWriterPtr nominatim_exportXMLStart(const char *structuredoutputfile) { xmlTextWriterPtr writer; writer = xmlNewTextWriterFilename(structuredoutputfile, 0); if (writer==NULL) { fprintf(stderr, "Unable to open %s\n", structuredoutputfile); exit(EXIT_FAILURE); } xmlTextWriterSetIndent(writer, 1); if (xmlTextWriterStartDocument(writer, NULL, "UTF8", NULL) < 0) { fprintf(stderr, "xmlTextWriterStartDocument failed\n"); exit(EXIT_FAILURE); } if (xmlTextWriterStartElement(writer, BAD_CAST "osmStructured") < 0) { fprintf(stderr, "xmlTextWriterStartElement failed\n"); exit(EXIT_FAILURE); } if (xmlTextWriterWriteAttribute(writer, BAD_CAST "version", BAD_CAST "0.1") < 0) { fprintf(stderr, "xmlTextWriterWriteAttribute failed\n"); exit(EXIT_FAILURE); } if (xmlTextWriterWriteAttribute(writer, BAD_CAST "generator", BAD_CAST "Nominatim") < 0) { fprintf(stderr, "xmlTextWriterWriteAttribute failed\n"); exit(EXIT_FAILURE); } mode = 0; return writer; } void nominatim_exportXMLEnd(xmlTextWriterPtr writer) { nominatim_exportEndMode(writer); // End if (xmlTextWriterEndElement(writer) < 0) { fprintf(stderr, "xmlTextWriterEndElement failed\n"); exit(EXIT_FAILURE); } if (xmlTextWriterEndDocument(writer) < 0) { fprintf(stderr, "xmlTextWriterEndDocument failed\n"); exit(EXIT_FAILURE); } xmlFreeTextWriter(writer); } void nominatim_exportStartMode(xmlTextWriterPtr writer, int newMode) { if (mode == newMode) return; nominatim_exportEndMode(writer); switch(newMode) { case 0: break; case 1: if (xmlTextWriterStartElement(writer, BAD_CAST "add") < 0) { fprintf(stderr, "xmlTextWriterStartElement failed\n"); exit(EXIT_FAILURE); } break; case 2: if (xmlTextWriterStartElement(writer, BAD_CAST "update") < 0) { fprintf(stderr, "xmlTextWriterStartElement failed\n"); exit(EXIT_FAILURE); } break; case 3: if (xmlTextWriterStartElement(writer, BAD_CAST "delete") < 0) { fprintf(stderr, "xmlTextWriterStartElement failed\n"); exit(EXIT_FAILURE); } break; } mode = newMode; } void nominatim_exportEndMode(xmlTextWriterPtr writer) { if (!mode) return; if (xmlTextWriterEndElement(writer) < 0) { fprintf(stderr, "xmlTextWriterEndElement failed\n"); exit(EXIT_FAILURE); } } void nominatim_exportPlaceQueries(uint64_t place_id, PGconn * conn, struct export_data * querySet) { const char * paramValues[1]; int paramLengths[1]; int paramFormats[1]; uint64_t paramPlaceID; paramPlaceID = PGint64(place_id); paramValues[0] = (char *)¶mPlaceID; paramLengths[0] = sizeof(paramPlaceID); paramFormats[0] = 1; querySet->res = PQexecPrepared(conn, "placex_details", 1, paramValues, paramLengths, paramFormats, 0); if (PQresultStatus(querySet->res) != PGRES_TUPLES_OK) { fprintf(stderr, "placex_details: SELECT failed: %s", PQerrorMessage(conn)); PQclear(querySet->res); exit(EXIT_FAILURE); } querySet->resNames = PQexecPrepared(conn, "placex_names", 1, paramValues, paramLengths, paramFormats, 0); if (PQresultStatus(querySet->resNames) != PGRES_TUPLES_OK) { fprintf(stderr, "placex_names: SELECT failed: %s", PQerrorMessage(conn)); PQclear(querySet->resNames); exit(EXIT_FAILURE); } querySet->resAddress = PQexecPrepared(conn, "placex_address", 1, paramValues, paramLengths, paramFormats, 0); if (PQresultStatus(querySet->resAddress) != PGRES_TUPLES_OK) { fprintf(stderr, "placex_address: SELECT failed: %s", PQerrorMessage(conn)); PQclear(querySet->resAddress); exit(EXIT_FAILURE); } querySet->resExtraTags = PQexecPrepared(conn, "placex_extratags", 1, paramValues, paramLengths, paramFormats, 0); if (PQresultStatus(querySet->resExtraTags) != PGRES_TUPLES_OK) { fprintf(stderr, "placex_extratags: SELECT failed: %s", PQerrorMessage(conn)); PQclear(querySet->resExtraTags); exit(EXIT_FAILURE); } } void nominatim_exportFreeQueries(struct export_data * querySet) { PQclear(querySet->res); PQclear(querySet->resNames); PQclear(querySet->resAddress); PQclear(querySet->resExtraTags); } /* * Requirements: the prepared queries must exist */ void nominatim_exportPlace(uint64_t place_id, PGconn * conn, xmlTextWriterPtr writer, pthread_mutex_t * writer_mutex, struct export_data * prevQuerySet) { struct export_data querySet; int i; nominatim_exportPlaceQueries(place_id, conn, &querySet); // Add, modify or delete? if (prevQuerySet) { if ((PQgetvalue(prevQuerySet->res, 0, 14) && strcmp(PQgetvalue(prevQuerySet->res, 0, 14), "100") == 0) || PQntuples(querySet.res) == 0) { // Delete if (writer_mutex) pthread_mutex_lock( writer_mutex ); nominatim_exportStartMode(writer, 3); xmlTextWriterStartElement(writer, BAD_CAST "feature"); xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "place_id", "%li", place_id); xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 0)); xmlTextWriterWriteAttribute(writer, BAD_CAST "id", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 1)); xmlTextWriterWriteAttribute(writer, BAD_CAST "key", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 2)); xmlTextWriterWriteAttribute(writer, BAD_CAST "value", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 3)); xmlTextWriterEndElement(writer); if (writer_mutex) pthread_mutex_unlock( writer_mutex ); nominatim_exportFreeQueries(&querySet); return; } if (PQgetvalue(prevQuerySet->res, 0, 14) && strcmp(PQgetvalue(prevQuerySet->res, 0, 14), "1") == 0) { // Add if (writer_mutex) pthread_mutex_lock( writer_mutex ); nominatim_exportStartMode(writer, 1); } else { // Update, but only if something has changed // TODO: detect changes if (writer_mutex) pthread_mutex_lock( writer_mutex ); nominatim_exportStartMode(writer, 2); } } else { // Add if (writer_mutex) pthread_mutex_lock( writer_mutex ); nominatim_exportStartMode(writer, 1); } xmlTextWriterStartElement(writer, BAD_CAST "feature"); xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "place_id", "%li", place_id); xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.res, 0, 0)); xmlTextWriterWriteAttribute(writer, BAD_CAST "id", BAD_CAST PQgetvalue(querySet.res, 0, 1)); xmlTextWriterWriteAttribute(writer, BAD_CAST "key", BAD_CAST PQgetvalue(querySet.res, 0, 2)); xmlTextWriterWriteAttribute(writer, BAD_CAST "value", BAD_CAST PQgetvalue(querySet.res, 0, 3)); xmlTextWriterWriteAttribute(writer, BAD_CAST "rank", BAD_CAST PQgetvalue(querySet.res, 0, 9)); xmlTextWriterWriteAttribute(writer, BAD_CAST "importance", BAD_CAST PQgetvalue(querySet.res, 0, 10)); xmlTextWriterWriteAttribute(writer, BAD_CAST "parent_place_id", BAD_CAST PQgetvalue(querySet.res, 0, 11)); xmlTextWriterWriteAttribute(writer, BAD_CAST "parent_type", BAD_CAST PQgetvalue(querySet.res, 0, 12)); xmlTextWriterWriteAttribute(writer, BAD_CAST "parent_id", BAD_CAST PQgetvalue(querySet.res, 0, 13)); if (PQntuples(querySet.resNames)) { xmlTextWriterStartElement(writer, BAD_CAST "names"); for (i = 0; i < PQntuples(querySet.resNames); i++) { xmlTextWriterStartElement(writer, BAD_CAST "name"); xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.resNames, i, 0)); xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.resNames, i, 1)); xmlTextWriterEndElement(writer); } xmlTextWriterEndElement(writer); } if (PQgetvalue(querySet.res, 0, 5) && strlen(PQgetvalue(querySet.res, 0, 5))) { xmlTextWriterStartElement(writer, BAD_CAST "houseNumber"); xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 5)); xmlTextWriterEndElement(writer); } if (PQgetvalue(querySet.res, 0, 8) && strlen(PQgetvalue(querySet.res, 0, 8))) { xmlTextWriterStartElement(writer, BAD_CAST "adminLevel"); xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 8)); xmlTextWriterEndElement(writer); } if (PQgetvalue(querySet.res, 0, 6) && strlen(PQgetvalue(querySet.res, 0, 6))) { xmlTextWriterStartElement(writer, BAD_CAST "countryCode"); xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 6)); xmlTextWriterEndElement(writer); } if (PQntuples(querySet.resAddress) > 0) { xmlTextWriterStartElement(writer, BAD_CAST "address"); for (i = 0; i < PQntuples(querySet.resAddress); i++) { xmlTextWriterStartElement(writer, BAD_CAST getRankLabel(atoi(PQgetvalue(querySet.resAddress, i, 5)))); xmlTextWriterWriteAttribute(writer, BAD_CAST "rank", BAD_CAST PQgetvalue(querySet.resAddress, i, 5)); xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.resAddress, i, 0)); xmlTextWriterWriteAttribute(writer, BAD_CAST "id", BAD_CAST PQgetvalue(querySet.resAddress, i, 1)); xmlTextWriterWriteAttribute(writer, BAD_CAST "key", BAD_CAST PQgetvalue(querySet.resAddress, i, 2)); xmlTextWriterWriteAttribute(writer, BAD_CAST "value", BAD_CAST PQgetvalue(querySet.resAddress, i, 3)); xmlTextWriterWriteAttribute(writer, BAD_CAST "distance", BAD_CAST PQgetvalue(querySet.resAddress, i, 4)); xmlTextWriterWriteAttribute(writer, BAD_CAST "isaddress", BAD_CAST PQgetvalue(querySet.resAddress, i, 6)); xmlTextWriterEndElement(writer); } xmlTextWriterEndElement(writer); } if (PQntuples(querySet.resExtraTags)) { xmlTextWriterStartElement(writer, BAD_CAST "tags"); for (i = 0; i < PQntuples(querySet.resExtraTags); i++) { xmlTextWriterStartElement(writer, BAD_CAST "tag"); xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.resExtraTags, i, 0)); xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.resExtraTags, i, 1)); xmlTextWriterEndElement(writer); } xmlTextWriterEndElement(writer); } xmlTextWriterStartElement(writer, BAD_CAST "osmGeometry"); xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 7)); xmlTextWriterEndElement(writer); xmlTextWriterEndElement(writer); // if (writer_mutex) pthread_mutex_unlock( writer_mutex ); nominatim_exportFreeQueries(&querySet); } const char * getRankLabel(int rank) { switch (rank) { case 0: case 1: return "continent"; case 2: case 3: return "sea"; case 4: case 5: case 6: case 7: return "country"; case 8: case 9: case 10: case 11: return "state"; case 12: case 13: case 14: case 15: return "county"; case 16: return "city"; case 17: return "town"; case 18: return "village"; case 19: return "unknown"; case 20: return "suburb"; case 21: return "postcode"; case 22: return "neighborhood"; case 23: return "postcode"; case 24: return "unknown"; case 25: return "postcode"; case 26: return "street"; case 27: return "access"; case 28: return "building"; case 29: default: return "other"; } }