Mirror of osmpng - Simple OSM tile downloader
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

589 lines
15 KiB

/* osmpng.cpp
* 2015, Felix Niederwanger
*
* Tool for downloading PNG titles from OSM and merge them together to a
* single PNG map file.
*
* Licensed under the conditions of GPLv3
*/
#include <dirent.h>
#include <iostream>
#include <string>
#include <vector>
#include <stdlib.h>
#include <sstream>
#include <signal.h>
#include <math.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/timeb.h>
#include <curl/curl.h>
#include <png++/png.hpp>
#include "String.hpp"
using namespace std;
// VERSION
#define VERSION "0.3 JUL 2015"
// Use float or double precision
#define REAL float
// Makro for printing stuff only when not quiet
#define COUT if(!quiet) cout
/* ==== GLOBAL PROGRAM VARIABLES ============================================ */
// Rectangle to be downloaded
static REAL bounds[4];
// True if we should be quiet
static bool quiet = false;
// Cache directory
static String cacheDir = "/tmp/.osmpng_cache";
// Destination file
static String destFile = "output.png";
// If cached files should be deleted
static bool deleteCached = true;
/* ==== INTERNAL PROGRAM VARIABLES ========================================== */
// Cached files
static vector<string> files;
// Get milliseconds since epoch
static unsigned long get_millis() {
timeb tb;
ftime(&tb);
return tb.millitm + (tb.time & 0xfffff) * 1000L;
}
// Get slippy map tile coordinates
/* See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames for details */
REAL getTileY(REAL latitude, REAL n) {
REAL rad_lat = latitude * M_PI / 180.0;
REAL sec = 1.0/cos(rad_lat);
return n * (1.0 - (log(tan(rad_lat) + sec) / M_PI)) / 2.0;
}
REAL getTileX(REAL longitude, REAL n) {
return n * ((longitude + 180.0) / 360.0);
}
inline REAL toReal(std::string str) { return atof(str.c_str()); }
//inline REAL toReal(const char* str) { return atof(str); }
inline int toInt(std::string str) { return atoi(str.c_str()); }
//inline int toInt(const char* str) { return atoi(str); }
REAL fround(const REAL f, const int precision) {
REAL prec = pow(10, precision);
int i = (int)(f * prec);
return (REAL)(i) / prec;
}
inline REAL fround(const REAL f) { return fround(f,2); }
static void clear_cached_files() {
for(vector<string>::iterator it = files.begin(); it != files.end(); it++) {
std::string file = *it;
remove(file.c_str());
}
}
static void printHeader() {
if(quiet) return;
else {
cout << "OSM title downloader version " << VERSION << endl;
cout << " 2018, Felix Niederwanger" << endl;
cout << endl;
}
}
static inline bool file_exists(string name) {
return file_exists(name.c_str());
}
static inline bool dir_exists(string name) {
DIR* dir = opendir(name.c_str());
if(dir) {
closedir(dir);
return true;
} else
return false;
}
// Get file size in bytes
static size_t get_file_size(std::string filename)
{
struct stat stat_buf;
int rc = stat(filename.c_str(), &stat_buf);
return rc == 0 ? stat_buf.st_size : -1;
}
// Callback for receiving http data
static size_t write_http(void *ptr, size_t size, size_t nmemb, FILE *stream) {
return fwrite(ptr, size, nmemb, stream);
}
// Download a slippy map tile to a given destination file
size_t download(int tileX, int tileY, int zoom, string file) {
stringstream ss;
static int i = 0;
switch(i++) {
case 0:
ss << "http://a.tile.openstreetmap.org/";
break;
case 1:
ss << "http://b.tile.openstreetmap.org/";
break;
default:
ss << "http://c.tile.openstreetmap.org/";
i = 0;
break;
}
ss << zoom << '/' << tileX << '/' << tileY << ".png";
String url = ss.str();
CURL *curl;
CURLcode code;
curl = curl_easy_init();
if (curl) {
FILE *fp = fopen(file.c_str(), "wb");
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_http);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
// Allow max. 10 redirections
// curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 10L);
code = curl_easy_perform(curl);
curl_easy_cleanup(curl);
fclose(fp);
if(code != CURLE_OK) {
ss.str(std::string());
ss << "CURL returned error code" << code << ".";
throw ss.str();
}
// Check response code
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
if (response_code != 200) {
if (response_code == 429) {
throw "Too many requests";
}
cerr << "http respose code " << response_code << endl;
throw "Invalid http response code";
}
return get_file_size(file);
} else
throw "Error setting up curl";
}
// Create standartized cache filename
std::string get_filename(int x, int y, int zoom) {
stringstream ss;
ss << cacheDir;
if(!cacheDir.endsWith('/')) ss << '/';
ss << zoom << '-' << x << '.' << y << ".png";
return ss.str();
}
// Header message, when an error occurred
static void error_help_msg() {
cerr << "A terrible error happend. Please consider in reporting a bug to "
<< "me. Write a email to felix@feldspaten.org including version of "
<< " the program and a detailed description, WHAT you did." << endl
<< "Visit the project homepage at GitHub: "
<< "https://github.com/grisu48/segeo" << endl;
}
// Singal callback function
static void signal_function(int sig_nr) {
switch(sig_nr) {
case SIGSEGV:
error_help_msg();
cerr << "Segmentation fault (SIGSEGV)" << endl;
exit(101);
case SIGINT:
case SIGTERM:
cerr << "Caught cancel signal. Cleaning up ... ";
cerr.flush();
if(deleteCached) clear_cached_files();
cerr << "done" << endl;
exit(42);
}
}
// Merge routine to merge different PNG files.
// Writes the result to the given destination filename
static void merge(int* bounds, int zoom, std::string destination) {
size_t width, height;
size_t total_width, total_height;
std::string file = get_filename(bounds[0],bounds[2], zoom);
try {
png::image<png::rgb_pixel> source(file.c_str());
width = source.get_width();
height = source.get_height();
total_width = width * (bounds[1]-bounds[0]+1);
total_height = height * (bounds[3]-bounds[2]+1);
} catch (png::error &e) {
cerr << "png error while processing " << file << ": " << e.what() << endl;
return;
}
// cout << "Creating picture (" << total_width << "x" << total_height << ") ... " << endl;
png::image<png::rgb_pixel> image(total_width, total_height);
size_t offset[2];
offset[0] = 0;
offset[1] = 0;
int index[2];
for(int x = bounds[0]; x<=bounds[1]; x++) {
index[0] = x - bounds[0];
offset[0]++;
offset[1]=0;
for(int y = bounds[2]; y<=bounds[3]; y++) {
size_t base[2];
index[1] = y - bounds[2];
file = get_filename(x,y, zoom);
png::image<png::rgb_pixel> source(file.c_str());
size_t c_width, c_height;
c_width = source.get_width();
c_height = source.get_height();
if (c_width != width) throw "Width of tile mismatch";
if (c_height != height) throw "Width of tile mismatch";
// Copy image pixels
base[0] = width * ( index[0] );
base[1] = height * ( index[1] );
// Copy pixel data
for(size_t p_x = 0; p_x < c_width; p_x++) {
for(size_t p_y = 0; p_y < c_height; p_y++) {
image.set_pixel(p_x + base[0],p_y + base[1],source.get_pixel(p_x,p_y));
}
}
offset[1]++;
}
}
// cout << "Writing destination file ... " << endl;
image.write(destination.c_str());
}
// Print help message
static void printHelp(char* progname) {
cout << "OSM tile downloader - Version " << VERSION << endl;
cout << "2015, Felix Niederwanger" << endl << endl;
cout << "Usage: " << progname << " [OPTIONS] [LONGITUDE LATITUDE ZOOM]" << endl;
cout << endl << "OPTIONS" << endl <<
"\t-help Print this help message" << endl <<
"\t-version Print program version" << endl <<
"\t--cache=CACHE" << endl <<
"\t-c CACHE Define cache directory" << endl;
cout << "\t-o OUTPUT Define output file" << endl <<
"\t--keep-cache" << endl <<
"\t-k Do not delete cached files after download" << endl;
cout << endl;
cout << "If the destination is given, LONGITUDE LATITUDE and ZOOM must be defined" << endl;
}
// Get as size (given in bytes) in a human readable approximation
static string sizeHumanReadable(size_t size) {
stringstream ss;
if (size > 1024) {
float kb = size / 1024.0;
if (kb > 1024.0)
ss << fround(kb / 1024.0) << " MiB";
else
ss << fround(kb,2) << " kiB";
} else
ss << size << " B";
return ss.str();
}
// Print a size (in bytes) to a human readable approximation
static void printSizeHumanReadable(size_t size) {
cout << sizeHumanReadable(size);
}
// Get as speed (given in bytes) in a human readable approximation
static string speedHumandReadable(double speed) {
stringstream ss;
if (speed > 1024.0) {
float kb = speed / 1024.0;
if (kb > 1024.0)
ss << fround(kb / 1024.0) << " MiB/s";
else
ss << fround(kb,2) << " kiB/s";
} else
ss << speed << " B/s";
return ss.str();
}
static void _mkdir(const char *dir) {
char tmp[1024];
char *p = NULL;
size_t len;
snprintf(tmp, sizeof(tmp),"%s",dir);
len = strlen(tmp);
if(tmp[len - 1] == '/')
tmp[len - 1] = 0;
for(p = tmp + 1; *p; p++)
if(*p == '/') {
*p = 0;
mkdir(tmp, S_IRWXU);
*p = '/';
}
mkdir(tmp, S_IRWXU);
}
int main(int argc, char** argv) {
// Register signal handler
signal(SIGINT, signal_function);
signal(SIGSEGV, signal_function);
// slon, slat and szoom are the LONGITUE LATITUDE ZOOM paramters
String slon, slat, szoom;
bool stdinInput = true; // If we must read LONGITUDE LATITUDE ZOOM from
// stdin
// Parse program arguments
if (argc > 1) {
int i = 0;
while(++i < argc) {
String arg = argv[i];
if(arg.size() == 0) continue;
bool isLast = i == argc-1;
if(arg.equalsIgnoreCase("--help") || arg.equalsIgnoreCase("-h")) {
printHelp(argv[0]);
exit(0);
} else if(arg == "--version" || arg == "-version") {
cout << VERSION << endl;
exit(0);
} else if(arg.startsWith("--cache=") && arg.size() > 8) {
cacheDir = arg.substr(8);
} else if(arg == "-c") {
if(isLast) continue;
cacheDir = argv[++i];
} else if(arg == "-o") {
if(isLast) continue;
destFile = argv[++i];
} else if(arg == "--keep-cache" || arg == "-k") {
// Keep cache
deleteCached = false;
} else if(arg == "-q") {
quiet = true;
} else {
// LONGITUDE LATITUDE ZOOM argument?
if (arg.at(0) == '-') {
cerr << "Illegal argument: " << argv[i] << endl;
return EXIT_FAILURE;
} else {
// Check if enough parameters left
if (i+1 >= argc) {
cerr << "If providing a LONGITUDE you must also provide a LATITUDE" << endl;
return EXIT_FAILURE;
}
slon = argv[i];
slat = argv[i+1];
if(i+2 >= argc) {
szoom = "12";
i++;
} else {
szoom = argv[i+2];
i+=2;
}
stdinInput = false;
}
}
}
// Print options if not quiet
if(!quiet) {
printHeader();
if(!deleteCached) cout << "Keeping cached files" << endl;
if(!stdinInput) {
cout << "Longitude: " << slon << endl
<< "Latitude : " << slat << endl
<< "Zoom: " << szoom << endl;
}
}
}
// cacheDir must end with "/"
if(cacheDir.isEmpty()) cacheDir = "./";
if(!cacheDir.endsWith('/')) cacheDir += '/';
// Read from stdin, if not yet given as program parameter
if (stdinInput) {
try {
COUT << "No input parameters given. Use " << argv[0] << " --help if you need help" << endl;
COUT << "Type in coordinates for the map to download" << endl;
COUT << " Longitude and Latitude can also be a range of coordinates" << endl;
COUT << endl;
cout << "Longitude : "; getline(cin, slon); if (cin.eof()) throw "";
cout << "Latitude : "; getline(cin, slat); if (cin.eof()) throw "";
cout << "Zoom : "; getline(cin, szoom);if (cin.eof()) throw "";
} catch (...) {
cerr << "Cancelled" << endl;
return EXIT_FAILURE;
}
}
// Parse LONGITUDE LATITUDE ZOOM
int zoom = toInt(szoom);
REAL n = pow(2.0, zoom);
if(slon.contains("-")) {
std::vector<String> vec = slon.split('-');
bounds[0] = toReal(vec[0].trim());
bounds[1] = toReal(vec[1].trim());
} else {
bounds[0] = toReal(slon);
bounds[1] = bounds[0];
}
if(slat.contains("-")) {
std::vector<String> vec = slat.split('-');
bounds[2] = toReal(vec[0].trim());
bounds[3] = toReal(vec[1].trim());
} else {
bounds[2] = toReal(slat);
bounds[3] = bounds[2];
}
// Check bounds and organise bounds
try {
for(int i=0;i<2;i++) {
if(bounds[i] < -180.0) throw "Longitude < -180 degree";
if(bounds[i] > 180.0) throw "Longitude > 180 degree";
}
for(int i=2;i<4;i++) {
if(bounds[i] < -90.0) throw "Latitude < -90 degree";
if(bounds[i] > 90.0) throw "Latitude > 90 degree";
}
} catch (const char* msg) {
cerr << "ERROR: Bounds invalid (" << msg << ")" << endl;
return EXIT_FAILURE;
}
// Create destination folder, if not existing
if(!dir_exists(cacheDir))
mkdir(cacheDir.c_str(), S_IRWXU | S_IRWXG);
// Calculate tile coordinates
bounds[0] = getTileX(bounds[0], n);
bounds[1] = getTileX(bounds[1], n);
bounds[2] = getTileY(bounds[2], n);
bounds[3] = getTileY(bounds[3], n);
if(bounds[0] > bounds[1]) {
REAL tmp = bounds[0];
bounds[0] = bounds[1];
bounds[1] = tmp;
}
if(bounds[2] > bounds[3]) {
REAL tmp = bounds[2];
bounds[2] = bounds[3];
bounds[3] = tmp;
}
// Create cache dir, if not yet done
_mkdir(cacheDir.c_str());
// Begin download
COUT << "Downloading tiles (" << bounds[0] << " - " << bounds[1] << ") - ("
<< bounds[2] << " - " << bounds[3] << ") ... " << endl;
COUT.flush();
int ibounds[4];
for (int i=0;i<4;i++)
ibounds[i] = (int)bounds[i];
int total = (bounds[1] - bounds[0] + 1) * (bounds[3] - bounds[2] + 1);
int progress = 0;
size_t total_size = 0;
unsigned long total_millis = -get_millis();
try {
for(int x=ibounds[0];x<=ibounds[1];x++) {
for (int y=ibounds[2];y<=ibounds[3];y++) {
COUT << " ["<< fround(100.0 * (REAL)progress / (REAL)total) << "%]"
<< "\tDownloading tile [" << x << "-" << y << "] ... ";
COUT.flush();
std::string file = get_filename(x,y,zoom);
files.push_back(file);
unsigned long millis = -get_millis();
size_t size = download(x,y,zoom, file);
millis += get_millis();
total_size += size;
progress++;
if (!quiet) {
double speed = fround(size*1000.0/(double)millis);
printSizeHumanReadable(size);
cout << " @ " << speedHumandReadable(speed);
cout << " \r";
cout.flush();
}
sleep(1);
}
}
total_millis += get_millis();
if (!quiet) {
double speed = fround(total_size*1000.0/(double)total_millis);
cout << "Downloaded totally ";
printSizeHumanReadable(total_size);
cout << " within " << total_millis << " ms @ "
<< speedHumandReadable(speed)
<< " " << endl;
}
} catch (string &msg) {
cerr << msg << endl;
exit(EXIT_FAILURE);
} catch (const char *msg) {
cerr << msg << endl;
exit(EXIT_FAILURE);
}
COUT << "Merging tiles ... ";
COUT.flush();
merge(ibounds, zoom, destFile);
COUT << "done" << " \r";
if(deleteCached) {
COUT << "Clearing cache ... ";
COUT.flush();
clear_cached_files();
COUT << "done" << endl;
}
COUT << endl;
return EXIT_SUCCESS;
}