/*************************************************************************** * Project: osmdata * File: convert_osm_rcpp.cpp * Language: C++ * * osmdata is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * osmdata is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * osm-router. If not, see . * * Author: Mark Padgham * E-Mail: mark.padgham@email.com * * Description: Functions to convert OSM data stored in C++ arrays to Rcpp * structures. * * Limitations: * * Dependencies: none (rapidXML header included in osmdatar) * * Compiler Options: -std=c++11 ***************************************************************************/ #include "convert-osm-rcpp.h" /************************************************************************ ************************************************************************ ** ** ** FUNCTIONS TO CONVERT C++ OBJECTS TO Rcpp::List OBJECTS ** ** ** ************************************************************************ ************************************************************************/ /* Traces a single way and adds (lon,lat,rownames) to an Rcpp::NumericMatrix * * @param &ways pointer to Ways structure * @param &nodes pointer to Nodes structure * @param first_node Last node of previous way to find in current * @param &wayi_id pointer to ID of current way * @lons pointer to vector of longitudes * @lats pointer to vector of latitutdes * @rownames pointer to vector of rownames for each node. * @nmat Rcpp::NumericMatrix to store lons, lats, and rownames */ void osm_convert::trace_way_nmat (const Ways &ways, const Nodes &nodes, const osmid_t &wayi_id, Rcpp::NumericMatrix &nmat) { auto wayi = ways.find (wayi_id); std::vector rownames; rownames.clear (); size_t n = wayi->second.nodes.size (); rownames.reserve (n); nmat = Rcpp::NumericMatrix (Rcpp::Dimension (n, 2)); size_t tempi = 0; for (auto ni = wayi->second.nodes.begin (); ni != wayi->second.nodes.end (); ++ni) { rownames.push_back (std::to_string (*ni)); //nmat (tempi, 0) = static_cast (nodes.find (*ni)->second.lon); //nmat (tempi++, 1) = static_cast (nodes.find (*ni)->second.lat); nmat (tempi, 0) = nodes.find (*ni)->second.lon; nmat (tempi++, 1) = nodes.find (*ni)->second.lat; } std::vector colnames = {"lon", "lat"}; Rcpp::List dimnames (0); dimnames.push_back (rownames); dimnames.push_back (colnames); nmat.attr ("dimnames") = dimnames; //dimnames.erase (0, static_cast (dimnames.size ())); dimnames.erase (0, 2); } /* get_value_mat_way * * Extract key-value pairs for a given way and fill Rcpp::CharacterMatrix * * @param wayi Constant iterator to one OSM way * @param Ways Pointer to the std::vector of all ways * @param unique_vals Pointer to the UniqueVals structure * @param value_arr Pointer to the Rcpp::CharacterMatrix of values to be filled * by tracing the key-val pairs of the way 'wayi' * @param rowi Integer value for the key-val pairs for wayi */ // TODO: Is it faster to use a std::vector instead of // Rcpp::CharacterMatrix and then simply // Rcpp::CharacterMatrix mat (nrow, ncol, value_vec.begin ()); ? void osm_convert::get_value_mat_way (Ways::const_iterator wayi, const UniqueVals &unique_vals, Rcpp::CharacterMatrix &value_arr, unsigned int rowi) { for (auto kv_iter = wayi->second.key_val.begin (); kv_iter != wayi->second.key_val.end (); ++kv_iter) { const std::string &key = kv_iter->first; unsigned int coli = unique_vals.k_way_index.at (key); value_arr (rowi, coli) = kv_iter->second; } } /* get_value_mat_rel * * Extract key-value pairs for a given relation and fill Rcpp::CharacterMatrix * * @param reli Constant iterator to one OSM relation * @param rels Pointer to the std::vector of all relations * @param unique_vals Pointer to the UniqueVals structure * @param value_arr Pointer to the Rcpp::CharacterMatrix of values to be filled * by tracing the key-val pairs of the relation 'reli' * @param rowi Integer value for the key-val pairs for reli */ void osm_convert::get_value_mat_rel (Relations::const_iterator &reli, const UniqueVals &unique_vals, Rcpp::CharacterMatrix &value_arr, unsigned int rowi) { for (auto kv_iter = reli->key_val.begin (); kv_iter != reli->key_val.end (); ++kv_iter) { const std::string &key = kv_iter->first; unsigned int coli = unique_vals.k_rel_index.at (key); value_arr (rowi, coli) = kv_iter->second; } } /* restructure_kv_mat * * Restructures a key-value matrix to reflect typical GDAL output by * inserting a first column containing "osm_id" and moving the "name" column to * second position. * * @param kv Pointer to the key-value matrix to be restructured * @param ls true only for multilinestrings, which have compound rownames which * include roles */ Rcpp::CharacterMatrix osm_convert::restructure_kv_mat (Rcpp::CharacterMatrix &kv, bool ls=false) { // The following has to be done in 2 lines: std::vector > dims = kv.attr ("dimnames"); std::vector ids = dims [0], varnames = dims [1], varnames_new; Rcpp::CharacterMatrix kv_out; int ni = static_cast (std::distance (varnames.begin (), std::find (varnames.begin (), varnames.end (), "name"))); unsigned int add_lines = 1; if (ls) add_lines++; // This only processes entries that have key = "name". Those that don't do // not get their osm_id values appended. It's then easier to post-append // these, rather than modify this code if (ni < static_cast (varnames.size ())) { Rcpp::CharacterVector name_vals = kv.column (ni); Rcpp::CharacterVector roles; // only for ls, but has to be defined here // convert ids to CharacterVector - direct allocation doesn't work Rcpp::CharacterVector ids_rcpp (ids.size ()); if (!ls) for (unsigned int i=0; i (kv.ncol ()) + add_lines); varnames_new.push_back ("osm_id"); varnames_new.push_back ("name"); kv_out = Rcpp::CharacterMatrix (Rcpp::Dimension ( static_cast (kv.nrow ()), static_cast (kv.ncol ()) + add_lines)); kv_out.column (0) = ids_rcpp; kv_out.column (1) = name_vals; if (ls) { varnames_new.push_back ("role"); kv_out.column (2) = roles; } int count = 1 + static_cast (add_lines); int i_int = 0; for (unsigned int i=0; i (kv.ncol ()); i++) { if (i != static_cast (ni)) { varnames_new.push_back (varnames [i]); kv_out.column (count++) = kv.column (i_int); } i_int++; } kv_out.attr ("dimnames") = Rcpp::List::create (ids, varnames_new); } else kv_out = kv; return kv_out; } /* convert_poly_linestring_to_sf * * Converts the data contained in all the arguments into an Rcpp::List object * to be used as the geometry column of a Simple Features Colletion * * @param lon_arr 3D array of longitudinal coordinates * @param lat_arr 3D array of latgitudinal coordinates * @param rowname_arr 3D array of IDs for nodes of all (lon,lat) * @param id_vec 2D array of either or IDs for all ways * used in the geometry. * @param rel_id Vector of IDs for each relation. * * @return An Rcpp::List object of [relation][way][node/geom] data. */ // TODO: Replace return value with pointer to List as argument? template Rcpp::List osm_convert::convert_poly_linestring_to_sf ( const double_arr3 &lon_arr, const double_arr3 &lat_arr, const string_arr3 &rowname_arr, const std::vector > &id_vec, const std::vector &rel_id, const std::string type) { if (!(type == "MULTILINESTRING" || type == "MULTIPOLYGON")) throw std::runtime_error ("type must be multilinestring/polygon"); // # nocov Rcpp::List outList (lon_arr.size ()); Rcpp::NumericMatrix nmat (Rcpp::Dimension (0, 0)); Rcpp::List dimnames (0); std::vector colnames = {"lat", "lon"}; for (unsigned int i=0; i (dimnames.size ())); dimnames.erase (0, 2); outList_i [j] = nmat; } outList_i.attr ("names") = id_vec [i]; if (type == "MULTIPOLYGON") { Rcpp::List tempList (1); tempList (0) = outList_i; tempList.attr ("class") = Rcpp::CharacterVector::create ("XY", type, "sfg"); outList [i] = tempList; } else { outList_i.attr ("class") = Rcpp::CharacterVector::create ("XY", type, "sfg"); outList [i] = outList_i; } } outList.attr ("names") = rel_id; return outList; } template Rcpp::List osm_convert::convert_poly_linestring_to_sf ( const double_arr3 &lon_arr, const double_arr3 &lat_arr, const string_arr3 &rowname_arr, const std::vector > &id_vec, const std::vector &rel_id, const std::string type); template Rcpp::List osm_convert::convert_poly_linestring_to_sf ( const double_arr3 &lon_arr, const double_arr3 &lat_arr, const string_arr3 &rowname_arr, const std::vector > &id_vec, const std::vector &rel_id, const std::string type); /* convert_multipoly_to_sp * * Converts the data contained in all the arguments into a * SpatialPolygonsDataFrame * * @param lon_arr 3D array of longitudinal coordinates * @param lat_arr 3D array of latgitudinal coordinates * @param rowname_arr 3D array of IDs for nodes of all (lon,lat) * @param id_vec 2D array of either or IDs for all ways * used in the geometry. * @param rel_id Vector of IDs for each relation. * * @return Object pointed to by 'multipolygons' is constructed. */ void osm_convert::convert_multipoly_to_sp (Rcpp::S4 &multipolygons, const Relations &rels, const double_arr3 &lon_arr, const double_arr3 &lat_arr, const string_arr3 &rowname_arr, const string_arr2 &id_vec, const UniqueVals &unique_vals) { Rcpp::Environment sp_env = Rcpp::Environment::namespace_env ("sp"); Rcpp::Function Polygon = sp_env ["Polygon"]; Rcpp::Language polygons_call ("new", "Polygons"); size_t nrow = lon_arr.size (), ncol = unique_vals.k_rel.size (); Rcpp::CharacterMatrix kv_mat (Rcpp::Dimension (nrow, ncol)); std::fill (kv_mat.begin (), kv_mat.end (), NA_STRING); Rcpp::List outList (lon_arr.size ()); Rcpp::NumericMatrix nmat (Rcpp::Dimension (0, 0)); Rcpp::List dimnames (0); std::vector colnames = {"lat", "lon"}, rel_id; unsigned int npolys = 0; for (auto itr = rels.begin (); itr != rels.end (); itr++) if (itr->ispoly) npolys++; if (npolys != lon_arr.size ()) throw std::runtime_error ("polygons must be same size as geometries"); // # nocov rel_id.reserve (npolys); unsigned int i = 0; for (auto itr = rels.begin (); itr != rels.end (); itr++) if (itr->ispoly) { Rcpp::List outList_i (lon_arr [i].size ()); // j over all ways, with outer always first followed by inner bool outer = true; //std::vector plotorder (lon_arr [i].size ()); Rcpp::IntegerVector plotorder (lon_arr [i].size ()); for (unsigned int j=0; j (dimnames.size ())); dimnames.erase (0, 2); Rcpp::S4 poly = Polygon (nmat); poly.slot ("hole") = !outer; poly.slot ("ringDir") = static_cast (1); if (outer) outer = false; else poly.slot ("ringDir") = static_cast (-1); outList_i [j] = poly; plotorder [j] = static_cast (j) + 1; // 1-based R values } outList_i.attr ("names") = id_vec [i]; Rcpp::S4 polygons = polygons_call.eval (); polygons.slot ("Polygons") = outList_i; // Issue #36 caused by data with one item having no actual data for // one item, so id_vec[i].size = lon_vec[i].size = ... = 0 if (id_vec [i].size () > 0) { // convert id_vec to single string std::string id_vec_str = id_vec [i] [0]; for (unsigned int j = 1; j < static_cast (id_vec [i].size ()); j++) id_vec_str += "." + id_vec [i] [j]; polygons.slot ("ID") = id_vec_str; } //polygons.slot ("ID") = id_vec [i]; // sp expects char not vec! polygons.slot ("plotOrder") = plotorder; //polygons.slot ("labpt") = poly.slot ("labpt"); //polygons.slot ("area") = poly.slot ("area"); outList [i] = polygons; rel_id.push_back (std::to_string (itr->id)); osm_convert::get_value_mat_rel (itr, unique_vals, kv_mat, i++); } // end if ispoly & for i outList.attr ("names") = rel_id; Rcpp::Language sp_polys_call ("new", "SpatialPolygonsDataFrame"); multipolygons = sp_polys_call.eval (); multipolygons.slot ("polygons") = outList; // Fill plotOrder slot with int vector - this has to be int, not // unsigned int! std::vector plotord (rels.size ()); for (size_t j = 0; j < rels.size (); j++) plotord [j] = static_cast (j + 1); multipolygons.slot ("plotOrder") = plotord; plotord.clear (); Rcpp::DataFrame kv_df = R_NilValue; if (rel_id.size () > 0) { kv_mat.attr ("names") = unique_vals.k_rel; kv_mat.attr ("dimnames") = Rcpp::List::create (rel_id, unique_vals.k_rel); kv_mat.attr ("names") = unique_vals.k_rel; if (kv_mat.nrow () > 0 && kv_mat.ncol () > 0) kv_df = osm_convert::restructure_kv_mat (kv_mat, false); multipolygons.slot ("data") = kv_df; } rel_id.clear (); } /* convert_multiline_to_sp * * Converts the data contained in all the arguments into a SpatialLinesDataFrame * * @param lon_arr 3D array of longitudinal coordinates * @param lat_arr 3D array of latgitudinal coordinates * @param rowname_arr 3D array of IDs for nodes of all (lon,lat) * @param id_vec 2D array of either or IDs for all ways * used in the geometry. * @param rel_id Vector of IDs for each relation. * * @return Object pointed to by 'multilines' is constructed. */ void osm_convert::convert_multiline_to_sp (Rcpp::S4 &multilines, const Relations &rels, const double_arr3 &lon_arr, const double_arr3 &lat_arr, const string_arr3 &rowname_arr, const osmt_arr2 &id_vec, const UniqueVals &unique_vals) { Rcpp::Language line_call ("new", "Line"); Rcpp::Language lines_call ("new", "Lines"); Rcpp::NumericMatrix nmat (Rcpp::Dimension (0, 0)); Rcpp::List dimnames (0); std::vector colnames = {"lat", "lon"}, rel_id; unsigned int nlines = 0; for (auto itr = rels.begin (); itr != rels.end (); itr++) if (!itr->ispoly) nlines++; rel_id.reserve (nlines); Rcpp::List outList (nlines); size_t ncol = unique_vals.k_rel.size (); Rcpp::CharacterMatrix kv_mat (Rcpp::Dimension (nlines, ncol)); std::fill (kv_mat.begin (), kv_mat.end (), NA_STRING); unsigned int i = 0; for (auto itr = rels.begin (); itr != rels.end (); itr++) if (!itr->ispoly) { Rcpp::List outList_i (lon_arr [i].size ()); // j over all ways for (unsigned int j=0; j (dimnames.size ())); dimnames.erase (0, 2); Rcpp::S4 line = line_call.eval (); line.slot ("coords") = nmat; outList_i [j] = line; } outList_i.attr ("names") = id_vec [i]; // implicit type conversion Rcpp::S4 lines = lines_call.eval (); lines.slot ("Lines") = outList_i; lines.slot ("ID") = itr->id; outList [i] = lines; rel_id.push_back (std::to_string (itr->id)); osm_convert::get_value_mat_rel (itr, unique_vals, kv_mat, i++); } // end if ispoly & for i outList.attr ("names") = rel_id; Rcpp::Language sp_lines_call ("new", "SpatialLinesDataFrame"); multilines = sp_lines_call.eval (); multilines.slot ("lines") = outList; Rcpp::DataFrame kv_df = R_NilValue; if (rel_id.size () > 0) { kv_mat.attr ("names") = unique_vals.k_rel; kv_mat.attr ("dimnames") = Rcpp::List::create (rel_id, unique_vals.k_rel); kv_mat.attr ("names") = unique_vals.k_rel; if (kv_mat.nrow () > 0 && kv_mat.ncol () > 0) kv_df = osm_convert::restructure_kv_mat (kv_mat, true); multilines.slot ("data") = kv_df; } rel_id.clear (); } /* convert_relation_to_sc * * Converts the data contained in all the arguments into an SC object * * @param id_vec 2D array of either or IDs for all ways * used in the geometry. * * @return Objects pointed to by 'members_out' and 'kv_out' are constructed. */ void osm_convert::convert_relation_to_sc (string_arr2 &members_out, string_arr2 &kv_out, const Relations &rels, const UniqueVals &unique_vals) { size_t nmembers = 0; for (auto itr = rels.begin (); itr != rels.end (); itr++) nmembers += itr->relations.size (); members_out.resize (nmembers); for (auto m: members_out) m.resize (3); size_t ncol = unique_vals.k_rel.size (); kv_out.resize (ncol); for (auto k: kv_out) k.resize (rels.size ()); unsigned int rowi = 0; // explicit loop index - see note at top of osmdata-sf for (auto itr = rels.begin (); itr != rels.end (); itr++) { //const unsigned int rowi = static_cast ( // std::distance (rels.begin (), itr)); // Get all members of that relation and their roles: unsigned int rowj = rowi; for (auto ritr = itr->relations.begin (); ritr != itr->relations.end (); ++ritr) { //const unsigned int rowj = rowi + static_cast ( // std::distance (itr->relations.begin (), ritr)); members_out [rowj] [0] = std::to_string (itr->id); members_out [rowj] [1] = std::to_string (ritr->first); // OSM id members_out [rowj++] [2] = ritr->second; // role } // And then key-value pairs for (auto kv_iter = itr->key_val.begin (); kv_iter != itr->key_val.end (); ++kv_iter) { const std::string &key = kv_iter->first; //long int coli = static_cast ( // unique_vals.k_rel_index.at (key)); unsigned int coli = unique_vals.k_rel_index.at (key); kv_out [coli] [rowi] = kv_iter->second; } rowi++; } // end for itr }