├── .gitignore ├── LICENSE ├── README.md ├── config └── ngx_http_mbtiles_module.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 PACE Telematics GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## What is this? 2 | 3 | A nginx module to serve map tiles directly from mbtiles container files. 4 | 5 | ## Nginx configuration example 6 | 7 | * mbtiles_file - points to the mbtiles file 8 | 9 |
10 |   location ~ ^/(.*?)/(.*?)/(.*?)/(.*?)$ {
11 |       mbtiles_file "$document_root/$1.mbtiles";
12 |       mbtiles_zoom "$2";
13 |       mbtiles_column "$3";
14 |       mbtiles_row "$4";
15 |   }
16 | 
17 | 
18 | 19 | ## Installation 20 | 21 | ### Prerequisits 22 | 23 | You need to have a `sqlite3-dev` package installed. On Ubuntu or Debian you can install it using: 24 | 25 | ```sh 26 | apt-get install libsqlite3-dev 27 | ``` -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_mbtiles_module 2 | 3 | if test -n "$ngx_module_link"; then 4 | ngx_module_type=HTTP 5 | ngx_module_name=ngx_http_mbtiles_module 6 | ngx_module_srcs="$ngx_addon_dir/ngx_http_mbtiles_module.c" 7 | ngx_module_libs="-lsqlite3" 8 | 9 | . auto/module 10 | else 11 | HTTP_MODULES="$HTTP_MODULES ngx_http_mbtiles_module" 12 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mbtiles_module.c" 13 | CORE_LIBS="$CORE_LIBS -lsqlite3" 14 | fi -------------------------------------------------------------------------------- /ngx_http_mbtiles_module.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_mbtiles_module.c 3 | * @author Philip Blatter 4 | * @date Fri Sep 03 08:08:12 2017 5 | * 6 | * @brief This modules serves map tiles from mbtiles container files 7 | * 8 | * @section LICENSE 9 | * 10 | * Copyright (c) 2017 PACE Telematics GmbH 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | typedef struct { 37 | ngx_http_complex_value_t *mbtiles_file; 38 | ngx_http_complex_value_t *mbtiles_zoom; 39 | ngx_http_complex_value_t *mbtiles_column; 40 | ngx_http_complex_value_t *mbtiles_row; 41 | } ngx_http_mbtiles_loc_conf_t; 42 | 43 | //static char *ngx_http_mbtiles(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 44 | char *ngx_str_t_to_char_ptr(ngx_str_t str); 45 | static char *ngx_http_mbtiles_enable(ngx_conf_t *cf, void *cmd, void *conf); 46 | static ngx_int_t ngx_http_mbtiles_handler(ngx_http_request_t *r); 47 | 48 | static ngx_conf_post_handler_pt ngx_http_mbtiles_enable_p = ngx_http_mbtiles_enable; 49 | 50 | /** 51 | * This module let you read map tiles directly from a mbtiles file 52 | * and serve them as requested. 53 | */ 54 | static ngx_command_t ngx_http_mbtiles_commands[] = { 55 | { 56 | ngx_string("mbtiles_file"), 57 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 58 | ngx_http_set_complex_value_slot, 59 | NGX_HTTP_LOC_CONF_OFFSET, 60 | offsetof(ngx_http_mbtiles_loc_conf_t, mbtiles_file), 61 | NULL, // this is not called for complex values?! -> &ngx_http_mbtiles_enable_p 62 | }, 63 | { 64 | ngx_string("mbtiles_zoom"), 65 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 66 | ngx_http_set_complex_value_slot, 67 | NGX_HTTP_LOC_CONF_OFFSET, 68 | offsetof(ngx_http_mbtiles_loc_conf_t, mbtiles_zoom), 69 | NULL 70 | }, 71 | { 72 | ngx_string("mbtiles_column"), 73 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 74 | ngx_http_set_complex_value_slot, 75 | NGX_HTTP_LOC_CONF_OFFSET, 76 | offsetof(ngx_http_mbtiles_loc_conf_t, mbtiles_column), 77 | NULL 78 | }, 79 | { 80 | ngx_string("mbtiles_row"), 81 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 82 | ngx_http_set_complex_value_slot, 83 | NGX_HTTP_LOC_CONF_OFFSET, 84 | offsetof(ngx_http_mbtiles_loc_conf_t, mbtiles_row), 85 | NULL 86 | }, 87 | 88 | ngx_null_command 89 | }; /* ngx_http_mbtiles_commands */ 90 | 91 | /** 92 | * Create local configuration 93 | * 94 | * @param r 95 | * Pointer to the request structure. 96 | * @return 97 | * Pointer to the configuration structure. 98 | */ 99 | static void * 100 | ngx_http_mbtiles_create_loc_conf(ngx_conf_t *cf) 101 | { 102 | ngx_http_mbtiles_loc_conf_t *conf; 103 | 104 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_mbtiles_loc_conf_t)); 105 | if (conf == NULL) { 106 | return NGX_CONF_ERROR; 107 | } 108 | 109 | return conf; 110 | } /* ngx_http_mbtiles_create_loc_conf */ 111 | 112 | /** 113 | * Merge configurations 114 | * 115 | * @param r 116 | * Pointer to the request structure. 117 | * @return 118 | * Status 119 | */ 120 | static char * 121 | ngx_http_mbtiles_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 122 | { 123 | ngx_http_mbtiles_loc_conf_t *prev = parent; 124 | ngx_http_mbtiles_loc_conf_t *conf = child; 125 | 126 | if (conf->mbtiles_file == NULL) { 127 | conf->mbtiles_file = prev->mbtiles_file; 128 | } 129 | if (conf->mbtiles_zoom == NULL) { 130 | conf->mbtiles_zoom = prev->mbtiles_zoom; 131 | } 132 | if (conf->mbtiles_column == NULL) { 133 | conf->mbtiles_column = prev->mbtiles_column; 134 | } 135 | if (conf->mbtiles_row == NULL) { 136 | conf->mbtiles_row = prev->mbtiles_row; 137 | } 138 | 139 | // Workaround because ngx_conf_post_handler is not called by 140 | // config engine on complex types 141 | if (conf->mbtiles_file != NULL) { 142 | return ngx_http_mbtiles_enable(cf, NULL, NULL); 143 | } 144 | 145 | return NGX_CONF_OK; 146 | } /* ngx_http_mbtiles_merge_loc_conf */ 147 | 148 | /* The module context. */ 149 | static ngx_http_module_t ngx_http_mbtiles_module_ctx = { 150 | NULL, /* preconfiguration */ 151 | NULL, /* postconfiguration */ 152 | 153 | NULL, /* create main configuration */ 154 | NULL, /* init main configuration */ 155 | 156 | NULL, /* create server configuration */ 157 | NULL, /* merge server configuration */ 158 | 159 | ngx_http_mbtiles_create_loc_conf, /* create location configuration */ 160 | ngx_http_mbtiles_merge_loc_conf /* merge location configuration */ 161 | }; /* ngx_http_mbtiles_module_ctx */ 162 | 163 | /* Module definition. */ 164 | ngx_module_t ngx_http_mbtiles_module = { 165 | NGX_MODULE_V1, 166 | &ngx_http_mbtiles_module_ctx, /* module context */ 167 | ngx_http_mbtiles_commands, /* module directives */ 168 | NGX_HTTP_MODULE, /* module type */ 169 | NULL, /* init master */ 170 | NULL, /* init module */ 171 | NULL, /* init process */ 172 | NULL, /* init thread */ 173 | NULL, /* exit thread */ 174 | NULL, /* exit process */ 175 | NULL, /* exit master */ 176 | NGX_MODULE_V1_PADDING 177 | }; /* ngx_http_mbtiles_module */ 178 | 179 | /** 180 | * Content handler. 181 | * 182 | * @param r 183 | * Request structure pointer 184 | * @return 185 | * Response status 186 | */ 187 | static ngx_int_t 188 | ngx_http_mbtiles_handler(ngx_http_request_t *r) 189 | { 190 | ngx_buf_t *b; 191 | ngx_chain_t out; 192 | ngx_str_t mbtiles_file; 193 | ngx_str_t mbtiles_zoom; 194 | ngx_str_t mbtiles_column; 195 | ngx_str_t mbtiles_row; 196 | sqlite3_stmt *sqlite_stmt; 197 | sqlite3 *sqlite_handle; 198 | unsigned int sqlite3_ret; 199 | unsigned char *tile_content; 200 | unsigned int tile_read_bytes; 201 | 202 | ngx_http_mbtiles_loc_conf_t *mbtiles_config; 203 | mbtiles_config = ngx_http_get_module_loc_conf(r, ngx_http_mbtiles_module); 204 | 205 | /* let's try to get our config vars from nginx configuration */ 206 | if (ngx_http_complex_value(r, mbtiles_config->mbtiles_file, &mbtiles_file) != NGX_OK 207 | || ngx_http_complex_value(r, mbtiles_config->mbtiles_zoom, &mbtiles_zoom) != NGX_OK 208 | || ngx_http_complex_value(r, mbtiles_config->mbtiles_column, &mbtiles_column) != NGX_OK 209 | || ngx_http_complex_value(r, mbtiles_config->mbtiles_row, &mbtiles_row) != NGX_OK 210 | ) { 211 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to read mbtiles module configuration settings."); 212 | return NGX_ERROR; 213 | } 214 | 215 | /* we're supporting just GET and HEAD requests */ 216 | if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) { 217 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Only GET and HEAD requests are supported by the mbtiles module."); 218 | return NGX_HTTP_NOT_ALLOWED; 219 | } 220 | 221 | /* get mbtiles file path with 0 termination */ 222 | char *mbtiles_file_path; 223 | if (!(mbtiles_file_path = ngx_str_t_to_char_ptr(mbtiles_file))) { 224 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate buffer."); 225 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 226 | } 227 | 228 | /* try to open mbtiles file */ 229 | if (SQLITE_OK != (sqlite3_ret = sqlite3_open_v2(mbtiles_file_path, &sqlite_handle, SQLITE_OPEN_READONLY, NULL))) { 230 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Opening '%s' failed", mbtiles_file_path); 231 | free(mbtiles_file_path); 232 | return NGX_HTTP_NOT_FOUND; 233 | } 234 | free(mbtiles_file_path); 235 | 236 | /* prepare our sql statement */ 237 | const char* select_query = "select tile_data from tiles where zoom_level=? and tile_column=? and tile_row=?"; 238 | const char* tail; 239 | if (SQLITE_OK != (sqlite3_ret = sqlite3_prepare_v2(sqlite_handle, select_query, strlen(select_query), &sqlite_stmt, &tail))) { 240 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Could not prepare tile data sql statelemt"); 241 | sqlite3_close(sqlite_handle); 242 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 243 | } 244 | 245 | /* bind our values */ 246 | if (SQLITE_OK != (sqlite3_ret = sqlite3_bind_text(sqlite_stmt, 1, (const char *) mbtiles_zoom.data, mbtiles_zoom.len, SQLITE_STATIC) 247 | || SQLITE_OK != sqlite3_bind_text(sqlite_stmt, 2, (const char *) mbtiles_column.data, mbtiles_column.len, SQLITE_STATIC) 248 | || SQLITE_OK != sqlite3_bind_text(sqlite_stmt, 3, (const char *) mbtiles_row.data, mbtiles_row.len, SQLITE_STATIC))) { 249 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Could not bind values to prepared statement"); 250 | sqlite3_close(sqlite_handle); 251 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 252 | } 253 | 254 | /* execute query */ 255 | if (SQLITE_ROW != (sqlite3_ret = sqlite3_step(sqlite_stmt))) { 256 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Could not find a tile (ret=%i) for zoom=%s, column=%s, row=%s", sqlite3_ret, mbtiles_zoom.data, mbtiles_column.data, mbtiles_row.data); 257 | sqlite3_close(sqlite_handle); 258 | return NGX_HTTP_NOT_FOUND; 259 | } 260 | 261 | /* allocate buffer for the file content */ 262 | tile_read_bytes = sqlite3_column_bytes(sqlite_stmt, 1); 263 | if (!(tile_content = ngx_palloc(r->pool, tile_read_bytes))) { 264 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer memory."); 265 | sqlite3_close(sqlite_handle); 266 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 267 | } 268 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Read %i bytes for tile.", tile_read_bytes); 269 | 270 | /* copy the result into our internal buffer */ 271 | ngx_memcpy(tile_content, sqlite3_column_blob(sqlite_stmt, 1), tile_read_bytes); 272 | 273 | /* close sqlite database */ 274 | sqlite3_close(sqlite_handle); 275 | 276 | /* set the content-type header. */ 277 | if (ngx_http_set_content_type(r) != NGX_OK) { 278 | // TODO: Read the content type from the mbtiles file and adjust mime type accordingly 279 | r->headers_out.content_type.len = sizeof("application/vnd.mapbox-vector-tile") - 1; 280 | r->headers_out.content_type.data = (u_char *) "application/vnd.mapbox-vector-tile"; 281 | } 282 | 283 | /* allocate a new buffer for sending out the reply. */ 284 | b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); 285 | 286 | if (b == NULL) { 287 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer."); 288 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 289 | } 290 | 291 | /* insertion in the buffer chain. */ 292 | out.buf = b; 293 | out.next = NULL; /* just one buffer */ 294 | 295 | b->pos = tile_content; 296 | b->last = tile_content + tile_read_bytes; 297 | b->memory = 1; 298 | b->last_buf = 1; 299 | 300 | /* sending the headers for the reply. */ 301 | r->headers_out.status = NGX_HTTP_OK; 302 | r->headers_out.content_length_n = tile_read_bytes; 303 | ngx_http_send_header(r); 304 | 305 | return ngx_http_output_filter(r, &out); 306 | } /* ngx_http_mbtiles_handler */ 307 | 308 | /** 309 | * Configuration setup function that installs the content handler. 310 | * 311 | * @param cf 312 | * Module configuration structure pointer. 313 | * @param cmd 314 | * Module directives structure pointer. 315 | * @param conf 316 | * Module configuration structure pointer. 317 | * @return string 318 | * Status of the configuration setup. 319 | */ 320 | static char * 321 | ngx_http_mbtiles_enable(ngx_conf_t *cf, void *cmd, void *conf) 322 | { 323 | ngx_http_core_loc_conf_t *clcf; /* pointer to core location configuration */ 324 | 325 | /* Install the mbtiles handler. */ 326 | clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); 327 | clcf->handler = ngx_http_mbtiles_handler; 328 | 329 | return NGX_CONF_OK; 330 | } /* ngx_http_mbtiles */ 331 | 332 | /** 333 | * Helper to convert ngx_str_t to char *. 334 | * 335 | * @param str 336 | * String as ngx_str_t struct. 337 | * @return string 338 | * Zero terminated char *. 339 | */ 340 | char * 341 | ngx_str_t_to_char_ptr(ngx_str_t str) 342 | { 343 | char *ret; 344 | ret = malloc(str.len+1); 345 | if (ret == NULL) return ret; 346 | 347 | memset(ret, 0, str.len+1); 348 | strncpy(ret, (char *)str.data, str.len); 349 | 350 | return ret; 351 | } /* ngx_str_t_to_char_ptr */ 352 | --------------------------------------------------------------------------------