├── .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 |
--------------------------------------------------------------------------------