tileserver-gl-light 5.0.0 → 5.1.0-pre.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,85 +7,209 @@ import clone from 'clone';
7
7
  import express from 'express';
8
8
  import { validateStyleMin } from '@maplibre/maplibre-gl-style-spec';
9
9
 
10
- import { fixUrl, allowedOptions } from './utils.js';
10
+ import {
11
+ allowedSpriteScales,
12
+ allowedSpriteFormats,
13
+ fixUrl,
14
+ readFile,
15
+ } from './utils.js';
11
16
 
12
17
  const httpTester = /^https?:\/\//i;
13
- const allowedSpriteScales = allowedOptions(['', '@2x', '@3x']);
14
- const allowedSpriteFormats = allowedOptions(['png', 'json']);
15
18
 
16
19
  export const serve_style = {
17
- init: (options, repo) => {
20
+ /**
21
+ * Initializes the serve_style module.
22
+ * @param {object} options Configuration options.
23
+ * @param {object} repo Repository object.
24
+ * @param {object} programOpts - An object containing the program options.
25
+ * @returns {express.Application} The initialized Express application.
26
+ */
27
+ init: function (options, repo, programOpts) {
28
+ const { verbose } = programOpts;
18
29
  const app = express().disable('x-powered-by');
19
-
30
+ /**
31
+ * Handles requests for style.json files.
32
+ * @param {express.Request} req - Express request object.
33
+ * @param {express.Response} res - Express response object.
34
+ * @param {express.NextFunction} next - Express next function.
35
+ * @param {string} req.params.id - ID of the style.
36
+ * @returns {Promise<void>}
37
+ */
20
38
  app.get('/:id/style.json', (req, res, next) => {
21
- const item = repo[req.params.id];
22
- if (!item) {
23
- return res.sendStatus(404);
39
+ const { id } = req.params;
40
+ if (verbose) {
41
+ console.log(
42
+ 'Handling style request for: /styles/%s/style.json',
43
+ String(id).replace(/\n|\r/g, ''),
44
+ );
24
45
  }
25
- const styleJSON_ = clone(item.styleJSON);
26
- for (const name of Object.keys(styleJSON_.sources)) {
27
- const source = styleJSON_.sources[name];
28
- source.url = fixUrl(req, source.url, item.publicUrl);
29
- if (typeof source.data == 'string') {
30
- source.data = fixUrl(req, source.data, item.publicUrl);
46
+ try {
47
+ const item = repo[id];
48
+ if (!item) {
49
+ return res.sendStatus(404);
31
50
  }
32
- }
33
- // mapbox-gl-js viewer cannot handle sprite urls with query
34
- if (styleJSON_.sprite) {
35
- if (Array.isArray(styleJSON_.sprite)) {
36
- styleJSON_.sprite.forEach((spriteItem) => {
37
- spriteItem.url = fixUrl(req, spriteItem.url, item.publicUrl);
38
- });
39
- } else {
40
- styleJSON_.sprite = fixUrl(req, styleJSON_.sprite, item.publicUrl);
51
+ const styleJSON_ = clone(item.styleJSON);
52
+ for (const name of Object.keys(styleJSON_.sources)) {
53
+ const source = styleJSON_.sources[name];
54
+ source.url = fixUrl(req, source.url, item.publicUrl);
55
+ if (typeof source.data == 'string') {
56
+ source.data = fixUrl(req, source.data, item.publicUrl);
57
+ }
41
58
  }
59
+ if (styleJSON_.sprite) {
60
+ if (Array.isArray(styleJSON_.sprite)) {
61
+ styleJSON_.sprite.forEach((spriteItem) => {
62
+ spriteItem.url = fixUrl(req, spriteItem.url, item.publicUrl);
63
+ });
64
+ } else {
65
+ styleJSON_.sprite = fixUrl(req, styleJSON_.sprite, item.publicUrl);
66
+ }
67
+ }
68
+ if (styleJSON_.glyphs) {
69
+ styleJSON_.glyphs = fixUrl(req, styleJSON_.glyphs, item.publicUrl);
70
+ }
71
+ return res.send(styleJSON_);
72
+ } catch (e) {
73
+ next(e);
42
74
  }
43
- if (styleJSON_.glyphs) {
44
- styleJSON_.glyphs = fixUrl(req, styleJSON_.glyphs, item.publicUrl);
45
- }
46
- return res.send(styleJSON_);
47
75
  });
48
76
 
77
+ /**
78
+ * Handles GET requests for sprite images and JSON files.
79
+ * @param {express.Request} req - Express request object.
80
+ * @param {express.Response} res - Express response object.
81
+ * @param {express.NextFunction} next - Express next function.
82
+ * @param {string} req.params.id - ID of the sprite.
83
+ * @param {string} [req.params.spriteID='default'] - ID of the specific sprite image, defaults to 'default'.
84
+ * @param {string} [req.params.scale] - Scale of the sprite image, defaults to ''.
85
+ * @param {string} req.params.format - Format of the sprite file, 'png' or 'json'.
86
+ * @returns {Promise<void>}
87
+ */
49
88
  app.get(
50
- '/:id/sprite(/:spriteID)?:scale(@[23]x)?.:format([\\w]+)',
51
- (req, res, next) => {
52
- const { spriteID = 'default', id } = req.params;
53
- const scale = allowedSpriteScales(req.params.scale) || '';
54
- const format = allowedSpriteFormats(req.params.format);
55
-
56
- if (format) {
57
- const item = repo[id];
58
- const sprite = item.spritePaths.find(
59
- (sprite) => sprite.id === spriteID,
89
+ `/:id/sprite{/:spriteID}{@:scale}{.:format}`,
90
+ async (req, res, next) => {
91
+ const { spriteID = 'default', id, format, scale } = req.params;
92
+ const sanitizedId = String(id).replace(/\n|\r/g, '');
93
+ const sanitizedScale = scale ? String(scale).replace(/\n|\r/g, '') : '';
94
+ const sanitizedSpriteID = String(spriteID).replace(/\n|\r/g, '');
95
+ const sanitizedFormat = format
96
+ ? '.' + String(format).replace(/\n|\r/g, '')
97
+ : '';
98
+ if (verbose) {
99
+ console.log(
100
+ `Handling sprite request for: /styles/%s/sprite/%s%s%s`,
101
+ sanitizedId,
102
+ sanitizedSpriteID,
103
+ sanitizedScale,
104
+ sanitizedFormat,
60
105
  );
61
- if (sprite) {
62
- const filename = `${sprite.path + scale}.${format}`;
63
- return fs.readFile(filename, (err, data) => {
64
- if (err) {
65
- console.log('Sprite load error:', filename);
66
- return res.sendStatus(404);
67
- } else {
68
- if (format === 'json')
69
- res.header('Content-type', 'application/json');
70
- if (format === 'png') res.header('Content-type', 'image/png');
71
- return res.send(data);
72
- }
73
- });
74
- } else {
75
- return res.status(400).send('Bad Sprite ID or Scale');
106
+ }
107
+ const item = repo[id];
108
+ const validatedFormat = allowedSpriteFormats(format);
109
+ if (!item || !validatedFormat) {
110
+ if (verbose)
111
+ console.error(
112
+ `Sprite item or format not found for: /styles/%s/sprite/%s%s%s`,
113
+ sanitizedId,
114
+ sanitizedSpriteID,
115
+ sanitizedScale,
116
+ sanitizedFormat,
117
+ );
118
+ return res.sendStatus(404);
119
+ }
120
+ const sprite = item.spritePaths.find(
121
+ (sprite) => sprite.id === spriteID,
122
+ );
123
+ const spriteScale = allowedSpriteScales(scale);
124
+ if (!sprite || spriteScale === null) {
125
+ if (verbose)
126
+ console.error(
127
+ `Bad Sprite ID or Scale for: /styles/%s/sprite/%s%s%s`,
128
+ sanitizedId,
129
+ sanitizedSpriteID,
130
+ sanitizedScale,
131
+ sanitizedFormat,
132
+ );
133
+ return res.status(400).send('Bad Sprite ID or Scale');
134
+ }
135
+
136
+ const modifiedSince = req.get('if-modified-since');
137
+ const cc = req.get('cache-control');
138
+ if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
139
+ if (
140
+ new Date(item.lastModified).getTime() ===
141
+ new Date(modifiedSince).getTime()
142
+ ) {
143
+ return res.sendStatus(304);
76
144
  }
77
- } else {
78
- return res.status(400).send('Bad Sprite Format');
145
+ }
146
+
147
+ const sanitizedSpritePath = sprite.path.replace(/^(\.\.\/)+/, '');
148
+ const filename = `${sanitizedSpritePath}${spriteScale}.${validatedFormat}`;
149
+ if (verbose) console.log(`Loading sprite from: %s`, filename);
150
+ try {
151
+ const data = await readFile(filename);
152
+
153
+ if (validatedFormat === 'json') {
154
+ res.header('Content-type', 'application/json');
155
+ } else if (validatedFormat === 'png') {
156
+ res.header('Content-type', 'image/png');
157
+ }
158
+ if (verbose)
159
+ console.log(
160
+ `Responding with sprite data for /styles/%s/sprite/%s%s%s`,
161
+ sanitizedId,
162
+ sanitizedSpriteID,
163
+ sanitizedScale,
164
+ sanitizedFormat,
165
+ );
166
+ res.set({ 'Last-Modified': item.lastModified });
167
+ return res.send(data);
168
+ } catch (err) {
169
+ if (verbose) {
170
+ console.error(
171
+ 'Sprite load error: %s, Error: %s',
172
+ filename,
173
+ String(err),
174
+ );
175
+ }
176
+ return res.sendStatus(404);
79
177
  }
80
178
  },
81
179
  );
82
180
 
83
181
  return app;
84
182
  },
85
- remove: (repo, id) => {
183
+ /**
184
+ * Removes an item from the repository.
185
+ * @param {object} repo Repository object.
186
+ * @param {string} id ID of the item to remove.
187
+ * @returns {void}
188
+ */
189
+ remove: function (repo, id) {
86
190
  delete repo[id];
87
191
  },
88
- add: (options, repo, params, id, publicUrl, reportTiles, reportFont) => {
192
+ /**
193
+ * Adds a new style to the repository.
194
+ * @param {object} options Configuration options.
195
+ * @param {object} repo Repository object.
196
+ * @param {object} params Parameters object containing style path
197
+ * @param {string} id ID of the style.
198
+ * @param {object} programOpts - An object containing the program options
199
+ * @param {Function} reportTiles Function for reporting tile sources.
200
+ * @param {Function} reportFont Function for reporting font usage
201
+ * @returns {boolean} true if add is succesful
202
+ */
203
+ add: function (
204
+ options,
205
+ repo,
206
+ params,
207
+ id,
208
+ programOpts,
209
+ reportTiles,
210
+ reportFont,
211
+ ) {
212
+ const { publicUrl } = programOpts;
89
213
  const styleFile = path.resolve(options.paths.styles, params.style);
90
214
 
91
215
  let styleFileData;
@@ -199,6 +323,7 @@ export const serve_style = {
199
323
  spritePaths,
200
324
  publicUrl,
201
325
  name: styleJSON.name,
326
+ lastModified: new Date().toUTCString(),
202
327
  };
203
328
 
204
329
  return true;