diff --git a/.travis.yml b/.travis.yml index b2b90fc0be..616b48812b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,13 +36,6 @@ matrix: jdk: oraclejdk8 before_install: - - sudo add-apt-repository -y ppa:kevinkreiser/libsodium - - sudo add-apt-repository -y ppa:kevinkreiser/libpgm - - sudo add-apt-repository -y ppa:kevinkreiser/zeromq3 - - sudo add-apt-repository -y ppa:kevinkreiser/czmq - - sudo apt-get update - - sudo apt-get install libzmq3-dev libczmq-dev - - git submodule update --init - source ./scripts/travis/before_install.sh diff --git a/paparazzi/src/paparazzi.cpp b/paparazzi/src/paparazzi.cpp index d4fa3209fb..c5ebfafc6a 100644 --- a/paparazzi/src/paparazzi.cpp +++ b/paparazzi/src/paparazzi.cpp @@ -1,20 +1,14 @@ #include "paparazzi.h" -#include - -using namespace Tangram; - #define AA_SCALE 1.0 -#define MAX_WAITING_TIME 100.0 #define IMAGE_DEPTH 4 #if PLATFORM_LINUX #include "platform_linux.h" -std::shared_ptr platform; #elif PLATFORM_OSX #include "platform_osx.h" -std::shared_ptr platform; #endif + #include "log.h" #include "gl/texture.h" @@ -23,7 +17,6 @@ std::shared_ptr platform; #include #include #include -#include #include #include #include @@ -31,11 +24,8 @@ std::shared_ptr platform; #include "stb_image_write.h" -const headers_t::value_type CORS{"Access-Control-Allow-Origin", "*"}; -const headers_t::value_type PNG_MIME{"Content-type", "image/png"}; -const headers_t::value_type TXT_MIME{"Content-type", "text/plain;charset=utf-8"}; -const std::regex TILE_REGEX("\\/(\\d*)\\/(\\d*)\\/(\\d*)\\.png"); +using namespace Tangram; unsigned long long timeStart; @@ -68,9 +58,9 @@ Paparazzi::Paparazzi() #if PLATFORM_LINUX UrlClient::Options urlClientOptions; urlClientOptions.numberOfThreads = 10; - platform = std::make_shared(urlClientOptions); + auto platform = std::make_shared(urlClientOptions); #elif PLATFORM_OSX - platform = std::make_shared(); + auto platform = std::make_shared(); #endif timeStart = getTime(); @@ -162,12 +152,11 @@ void Paparazzi::setSceneContent(const std::string &_yaml_content) { m_map->loadScene(name.c_str(), false, {SceneUpdate("global.sdk_mapzen_api_key", "mapzen-y6KCsnw")}); } -bool Paparazzi::update() { +bool Paparazzi::update(int32_t _maxWaitTime) { double startTime = getTime(); float delta = 0.0; - while (delta < MAX_WAITING_TIME) { - logMsg("Update: waiting %f\n", delta); + while (_maxWaitTime < 0 || delta < _maxWaitTime) { bool bFinish = m_map->update(10.); delta = float(getTime() - startTime); @@ -182,225 +171,20 @@ bool Paparazzi::update() { return false; } -struct coord_s { - /** @brief coordinate x or column value */ - uint32_t x; - /** @brief coordinate y or row value */ - uint32_t y; - /** @brief coordinate z or zoom value */ - uint32_t z; -}; - -static double radians_to_degrees(double radians) { - return radians * 180 / M_PI; -} - -static double degrees_to_radians(double degrees) { - return degrees * M_PI / 180; -} - -// http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames -// TODO make output into point -void coord_to_lnglat(coord_s *coord, double *out_lng_deg, double *out_lat_deg) { - double n = pow(2, coord->z); - double lng_deg = coord->x / n * 360.0 - 180.0; - double lat_rad = atan(sinh(M_PI * (1 - 2 * coord->y / n))); - double lat_deg = radians_to_degrees(lat_rad); - *out_lng_deg = lng_deg; - *out_lat_deg = lat_deg; -} - -// http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames -// make input point -void lnglat_to_coord(double lng_deg, double lat_deg, int zoom, coord_s *out) { - double lat_rad = degrees_to_radians(lat_deg); - double n = pow(2.0, zoom); - out->x = (lng_deg + 180.0) / 360.0 * n; - out->y = (1.0 - log(tan(lat_rad) + (1 / cos(lat_rad))) / M_PI) / 2.0 * n; - out->z = zoom; -} - -worker_t::result_t Paparazzi::work(const std::list& job, void* request_info){ - // false means this is going back to the client, there is no next stage of the pipeline - worker_t::result_t result{false, {}, ""}; - - // This type differs per protocol hence the void* fun - auto& info = *static_cast(request_info); - - // Try to generate a response - http_response_t response; - try { - // double start_call = getTime(); - - logging::INFO(std::string("Handle request: ") + static_cast(job.front().data())); - //TODO: - // - actually use/validate the request parameters - auto request = http_request_t::from_string(static_cast(job.front().data()), - job.front().size()); - - if (request.path == "/check") { - // ELB check - response = http_response_t(200, "OK", "OK", headers_t{CORS, TXT_MIME}, "HTTP/1.1"); - response.from_info(info); - result.messages.emplace_back(response.to_string()); - return result; - } - - // SCENE - // --------------------- - auto scene_itr = request.query.find("scene"); - if (scene_itr == request.query.cend() || scene_itr->second.size() == 0) { - // If there is NO SCENE QUERY value - if (request.body.empty()) - // if there is not POST body content return error... - throw std::runtime_error("Missing scene parameter"); - - // ... other whise load content - setSceneContent(request.body); - - // The size of the custom scene is unique enough - result.heart_beat = std::to_string(request.body.size()); - } else { - // If there IS a SCENE QUERRY value load it - setScene(scene_itr->second.front()); - - result.heart_beat = scene_itr->second.front(); - } - - bool size_and_pos = true; - float pixel_density = 1.0f; - - // SIZE - auto width_itr = request.query.find("width"); - if (width_itr == request.query.cend() || width_itr->second.size() == 0) - size_and_pos = false; - auto height_itr = request.query.find("height"); - if (height_itr == request.query.cend() || height_itr->second.size() == 0) - size_and_pos = false; - auto density_itr = request.query.find("density"); - if (density_itr != request.query.cend() && density_itr->second.size() > 0) - pixel_density = fmax(1.,std::stof(density_itr->second.front())); - - // POSITION - auto lat_itr = request.query.find("lat"); - if (lat_itr == request.query.cend() || lat_itr->second.size() == 0) - size_and_pos = false; - auto lon_itr = request.query.find("lon"); - if (lon_itr == request.query.cend() || lon_itr->second.size() == 0) - size_and_pos = false; - auto zoom_itr = request.query.find("zoom"); - if (zoom_itr == request.query.cend() || zoom_itr->second.size() == 0) - size_and_pos = false; - - std::smatch match; - - if (size_and_pos) { - // Set Map and OpenGL context size - setSize(std::stoi(width_itr->second.front()), - std::stoi(height_itr->second.front()), - pixel_density); - setPosition(std::stod(lon_itr->second.front()), - std::stod(lat_itr->second.front())); - setZoom(std::stof(zoom_itr->second.front())); - - } else if (std::regex_search(request.path, match, TILE_REGEX) && match.size() == 4) { - setSize(256, 256, pixel_density); - - int tile_coord[3] = {0,0,0}; - for (int i = 0; i < 3; i++) { - std::istringstream cur(match.str(i+1)); - cur >> tile_coord[i]; - } - coord_s tile; - tile.z = tile_coord[0]; - setZoom(tile.z); - - tile.x = tile_coord[1]; - tile.y = tile_coord[2]; - - double n = pow(2, tile.z); - double lng_deg = (tile.x + 0.5) / n * 360.0 - 180.0; - double lat_rad = atan(sinh(M_PI * (1 - 2 * (tile.y + 0.5) / n))); - double lat_deg = radians_to_degrees(lat_rad); - - setPosition(lng_deg, lat_deg); - } else { - throw std::runtime_error("Missing parameters to construct image"); - } - - // OPTIONAL tilt and rotation - // --------------------- - auto tilt_itr = request.query.find("tilt"); - if (tilt_itr != request.query.cend() && tilt_itr->second.size() != 0) { - // If TILT QUERRY is provided assigned ... - setTilt(std::stof(tilt_itr->second.front())); - } - else { - // othewise use default (0.) - setTilt(0.0f); - } +void Paparazzi::render(std::string& _image) { + m_glContext->makeCurrent(); - auto rotation_itr = request.query.find("rotation"); - if (rotation_itr != request.query.cend() && rotation_itr->second.size() != 0) { - // If ROTATION QUERRY is provided assigned ... - setRotation(std::stof(rotation_itr->second.front())); - } - else { - // othewise use default (0.) - setRotation(0.0f); - } - - // Time to render - // --------------------- - - if (!update()) { - throw std::runtime_error("Image creation timeout"); - } - - m_glContext->makeCurrent(); - - // Render Tangram Scene - //m_glContext->bind(); - m_map->render(); - - GL::finish(); - - //m_glContext->unbind(); - - // Once the main FBO is draw take a picture - //m_glContext->getPixelsAsString(image); - // double total_time = getTime()-start_call; - // LOG("TOTAL CALL: %f", total_time); - // LOG("TOTAL speed: %f millisec per pixel", (total_time/((m_width * m_height)/1000.0))); - - std::string image; - - // m_glContext->writeImage("test.ppm"); - - //Texture::flipImageData(m_glContext->buffer(), m_glContext->width(), m_glContext->height(), IMAGE_DEPTH); - - stbi_write_png_to_func([](void *context, void *data, int size) { - static_cast(context)->append(static_cast(data), size); - }, - &image, - m_glContext->width(), - m_glContext->height(), - IMAGE_DEPTH, - m_glContext->buffer(), - m_glContext->width() * IMAGE_DEPTH); - - response = http_response_t(200, "OK", image, headers_t{CORS, PNG_MIME}, "HTTP/1.1"); - } - catch(const std::exception& e) { - response = http_response_t(400, "Bad Request", e.what(), headers_t{CORS}, "HTTP/1.1"); - } - - response.from_info(info); - result.messages.emplace_back(response.to_string()); - - return result; -} + m_map->render(); -void Paparazzi::cleanup () { + GL::finish(); + stbi_write_png_to_func([](void *context, void *data, int size) { + static_cast(context)->append(static_cast(data), size); + }, + &_image, + m_glContext->width(), + m_glContext->height(), + IMAGE_DEPTH, + m_glContext->buffer(), + m_glContext->width() * IMAGE_DEPTH); } diff --git a/paparazzi/src/paparazzi.h b/paparazzi/src/paparazzi.h index c15e6ea7e0..5b8aec2049 100644 --- a/paparazzi/src/paparazzi.h +++ b/paparazzi/src/paparazzi.h @@ -4,9 +4,9 @@ #include //prime_server guts -#include -#include -using namespace prime_server; +// #include +// #include +// using namespace prime_server; #include "tangram.h" // Tangram-ES #include "headlessContext.h" @@ -16,20 +16,17 @@ class Paparazzi { Paparazzi(); ~Paparazzi(); - void setSize(const int &_width, const int &_height, const float &_density); - void setZoom(const float &_zoom); - void setTilt(const float &_deg); - void setRotation(const float &_deg); - void setScene(const std::string &_url); - void setSceneContent(const std::string &_yaml_content); - void setPosition(const double &_lon, const double &_lat); - - // prime_server stuff - worker_t::result_t work (const std::list& job, void* request_info); - void cleanup(); + void setSize(const int &_width, const int &_height, const float &_density); + void setZoom(const float &_zoom); + void setTilt(const float &_deg); + void setRotation(const float &_deg); + void setScene(const std::string &_url); + void setSceneContent(const std::string &_yaml_content); + void setPosition(const double &_lon, const double &_lat); + void render(std::string& _image); + bool update(int32_t _maxWaitTime = -1); protected: - bool update(); std::string m_scene; double m_lat; diff --git a/paparazzi/src/worker.cpp b/paparazzi/src/worker.cpp index 30167a7c10..bbab1563f8 100644 --- a/paparazzi/src/worker.cpp +++ b/paparazzi/src/worker.cpp @@ -7,14 +7,211 @@ using namespace prime_server; #include #include #include +#include +#include -// Paparazzi #include "paparazzi.h" +#define MAX_WAITING_TIME 100.0 + +const headers_t::value_type CORS{"Access-Control-Allow-Origin", "*"}; +const headers_t::value_type PNG_MIME{"Content-type", "image/png"}; +const headers_t::value_type TXT_MIME{"Content-type", "text/plain;charset=utf-8"}; + +const std::regex TILE_REGEX("\\/(\\d*)\\/(\\d*)\\/(\\d*)\\.png"); + +struct tile_s { + uint32_t x; + uint32_t y; + uint32_t z; +}; + +static double radians_to_degrees(double radians) { + return radians * 180 / M_PI; +} + +static double degrees_to_radians(double degrees) { + return degrees * M_PI / 180; +} + +// http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames +// TODO make output into point +void tile_to_lnglat(tile_s *tile, double *out_lng_deg, double *out_lat_deg) { + double n = pow(2, tile->z); + double lng_deg = tile->x / n * 360.0 - 180.0; + double lat_rad = atan(sinh(M_PI * (1 - 2 * tile->y / n))); + double lat_deg = radians_to_degrees(lat_rad); + *out_lng_deg = lng_deg; + *out_lat_deg = lat_deg; +} + +// http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames +// make input point +void lnglat_to_tile(double lng_deg, double lat_deg, int zoom, tile_s *out) { + double lat_rad = degrees_to_radians(lat_deg); + double n = pow(2.0, zoom); + out->x = (lng_deg + 180.0) / 360.0 * n; + out->y = (1.0 - log(tan(lat_rad) + (1 / cos(lat_rad))) / M_PI) / 2.0 * n; + out->z = zoom; +} + +worker_t::result_t workFn(Paparazzi& _map, const std::list& job, void* request_info){ + // false means this is going back to the client, there is no next stage of the pipeline + worker_t::result_t result{false, {}, ""}; + + // This type differs per protocol hence the void* fun + auto& info = *static_cast(request_info); + + // Try to generate a response + http_response_t response; + try { + // double start_call = getTime(); + + logging::INFO(std::string("Handle request: ") + static_cast(job.front().data())); + //TODO: + // - actually use/validate the request parameters + auto request = http_request_t::from_string(static_cast(job.front().data()), + job.front().size()); + + if (request.path == "/check") { + // ELB check + response = http_response_t(200, "OK", "OK", headers_t{CORS, TXT_MIME}, "HTTP/1.1"); + response.from_info(info); + result.messages.emplace_back(response.to_string()); + return result; + } + + // SCENE + // --------------------- + auto scene_itr = request.query.find("scene"); + if (scene_itr == request.query.cend() || scene_itr->second.size() == 0) { + // If there is NO SCENE QUERY value + if (request.body.empty()) + // if there is not POST body content return error... + throw std::runtime_error("Missing scene parameter"); + + // ... otherwise load content + _map.setSceneContent(request.body); + + // The size of the custom scene is unique enough + result.heart_beat = std::to_string(request.body.size()); + } else { + // If there IS a SCENE QUERRY value load it + _map.setScene(scene_itr->second.front()); + + result.heart_beat = scene_itr->second.front(); + } + + bool size_and_pos = true; + float pixel_density = 1.0f; + + // SIZE + auto width_itr = request.query.find("width"); + if (width_itr == request.query.cend() || width_itr->second.size() == 0) + size_and_pos = false; + auto height_itr = request.query.find("height"); + if (height_itr == request.query.cend() || height_itr->second.size() == 0) + size_and_pos = false; + auto density_itr = request.query.find("density"); + if (density_itr != request.query.cend() && density_itr->second.size() > 0) + pixel_density = std::max(1.f,std::stof(density_itr->second.front())); + + // POSITION + auto lat_itr = request.query.find("lat"); + if (lat_itr == request.query.cend() || lat_itr->second.size() == 0) + size_and_pos = false; + auto lon_itr = request.query.find("lon"); + if (lon_itr == request.query.cend() || lon_itr->second.size() == 0) + size_and_pos = false; + auto zoom_itr = request.query.find("zoom"); + if (zoom_itr == request.query.cend() || zoom_itr->second.size() == 0) + size_and_pos = false; + + std::smatch match; + + if (size_and_pos) { + // Set Map and OpenGL context size + _map.setSize(std::stoi(width_itr->second.front()), + std::stoi(height_itr->second.front()), + pixel_density); + _map.setPosition(std::stod(lon_itr->second.front()), + std::stod(lat_itr->second.front())); + _map.setZoom(std::stof(zoom_itr->second.front())); + + } else if (std::regex_search(request.path, match, TILE_REGEX) && match.size() == 4) { + _map.setSize(256, 256, pixel_density); + + int tile_coord[3] = {0,0,0}; + for (int i = 0; i < 3; i++) { + std::istringstream cur(match.str(i+1)); + cur >> tile_coord[i]; + } + tile_s tile; + tile.z = tile_coord[0]; + _map.setZoom(tile.z); + + tile.x = tile_coord[1]; + tile.y = tile_coord[2]; + + double n = pow(2, tile.z); + double lng_deg = (tile.x + 0.5) / n * 360.0 - 180.0; + double lat_rad = atan(sinh(M_PI * (1 - 2 * (tile.y + 0.5) / n))); + double lat_deg = radians_to_degrees(lat_rad); + + _map.setPosition(lng_deg, lat_deg); + } else { + throw std::runtime_error("Missing parameters to construct image"); + } + + // OPTIONAL tilt and rotation + // --------------------- + auto tilt_itr = request.query.find("tilt"); + if (tilt_itr != request.query.cend() && tilt_itr->second.size() != 0) { + // If TILT QUERRY is provided assigned ... + _map.setTilt(std::stof(tilt_itr->second.front())); + } + else { + // othewise use default (0.) + _map.setTilt(0.0f); + } + + auto rotation_itr = request.query.find("rotation"); + if (rotation_itr != request.query.cend() && rotation_itr->second.size() != 0) { + // If ROTATION QUERRY is provided assigned ... + _map.setRotation(std::stof(rotation_itr->second.front())); + } + else { + // othewise use default (0.) + _map.setRotation(0.0f); + } + + if (!_map.update(MAX_WAITING_TIME)) { + throw std::runtime_error("Image creation timeout"); + } + + std::string image; + + _map.render(image); + + response = http_response_t(200, "OK", image, headers_t{CORS, PNG_MIME}, "HTTP/1.1"); + } + catch(const std::exception& e) { + response = http_response_t(400, "Bad Request", e.what(), headers_t{CORS}, "HTTP/1.1"); + } + + response.from_info(info); + result.messages.emplace_back(response.to_string()); + + return result; +} + +void cleanupFn(Paparazzi& _map) {} + int main(int argc, char* argv[]) { //we need the location of the proxy and the loopback if(argc < 3) { - logging::ERROR("Usage: " + std::string(argv[0]) + " [tcp|ipc]://upstream_endpoint[:tcp_port] [tcp|ipc]://loopback_endpoint[:tcp_port]"); + logging::ERROR("Usage: " + std::string(argv[0]) + + " [tcp|ipc]://upstream_endpoint[:tcp_port] [tcp|ipc]://loopback_endpoint[:tcp_port]"); return EXIT_FAILURE; } //gets requests from the http server @@ -24,18 +221,18 @@ int main(int argc, char* argv[]) { //listen for requests zmq::context_t context; - Paparazzi paparazzi_worker{}; + Paparazzi paparazzi{}; worker_t worker(context, upstream_endpoint, "ipc:///dev/null", // downstream_proxy_endpoint loopback_endpoint, // result_endpoint "ipc:///dev/null", // interrupt_endpoint - std::bind(&Paparazzi::work, - std::ref(paparazzi_worker), + std::bind(&workFn, + std::ref(paparazzi), std::placeholders::_1, std::placeholders::_2), - std::bind(&Paparazzi::cleanup, - std::ref(paparazzi_worker))); + std::bind(&cleanupFn, + std::ref(paparazzi))); worker.work(); //listen for SIGINT and terminate if we hear it diff --git a/scripts/travis/before_install.sh b/scripts/travis/before_install.sh index 1cf7de3fed..5a56f8f493 100755 --- a/scripts/travis/before_install.sh +++ b/scripts/travis/before_install.sh @@ -3,6 +3,16 @@ set -e set -o pipefail +if [[ ${PLATFORM} == "linux" ]]; then + + sudo add-apt-repository -y ppa:kevinkreiser/libsodium + sudo add-apt-repository -y ppa:kevinkreiser/libpgm + sudo add-apt-repository -y ppa:kevinkreiser/zeromq3 + sudo add-apt-repository -y ppa:kevinkreiser/czmq + sudo apt-get update + sudo apt-get install libzmq3-dev libczmq-dev +fi + if [[ ${PLATFORM} == "android" ]]; then # Note: the right way to download these packages is through the Android Studio SDK manager,