From b2bd5eaa9628ecd3ee62007a2b2e4aad4fa492c4 Mon Sep 17 00:00:00 2001 From: Andrew Calcutt Date: Wed, 28 Sep 2022 14:41:55 -0400 Subject: [PATCH] Convert to esm module syntax (#606) * switch to esm module * Update package.json * change to maplibre package * fix tests * eslint * remove extra package updates * Delete package-lock.json * change 'fs' to 'node:fs' * put back node 10. without the package updates this still works * remove trailing commas * remove unassociated fix / formatting * remove unassociated fix * remove eslint from this PR * remove unassociated fix * lint * Merge remote-tracking branch 'upstream/master' into esm_update * fix mlgl * update maplibre-native to new version with arm64 * update minor version --- package.json | 12 +-- publish.js | 10 +- src/main.js | 59 ++++++----- src/serve_data.js | 42 ++++---- src/serve_font.js | 24 ++--- src/serve_light.js | 10 ++ src/serve_rendered.js | 227 ++++++++++++++++++++------------------- src/serve_style.js | 30 +++--- src/server.js | 235 +++++++++++++++++++++-------------------- src/utils.js | 28 +++-- test/metadata.js | 60 +++++------ test/setup.js | 14 ++- test/static.js | 8 +- test/style.js | 36 +++---- test/tiles_data.js | 8 +- test/tiles_rendered.js | 10 +- 16 files changed, 412 insertions(+), 401 deletions(-) create mode 100644 src/serve_light.js diff --git a/package.json b/package.json index 3e95ae9..82b048a 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "tileserver-gl", - "version": "4.0.0", + "version": "4.1.0", "description": "Map tile server for JSON GL styles - vector and server side generated raster tiles", "main": "src/main.js", "bin": "src/main.js", + "type": "module", "repository": { "type": "git", - "url": "https://github.com/maptiler/tileserver-gl.git" + "url": "git+https://github.com/maptiler/tileserver-gl.git" }, "license": "BSD-2-Clause", "engines": { @@ -18,11 +19,11 @@ }, "dependencies": { "@mapbox/glyph-pbf-composite": "0.0.3", - "@maplibre/maplibre-gl-native": "5.0.1-pre.0", - "@maplibre/maplibre-gl-style-spec": "17.0.1", "@mapbox/mbtiles": "0.12.1", "@mapbox/sphericalmercator": "1.2.0", "@mapbox/vector-tile": "1.3.1", + "@maplibre/maplibre-gl-native": "5.0.1-pre.6", + "@maplibre/maplibre-gl-style-spec": "17.0.1", "advanced-pool": "0.3.3", "canvas": "2.10.1", "chokidar": "3.5.3", @@ -30,7 +31,6 @@ "color": "4.2.3", "commander": "9.4.0", "cors": "2.8.5", - "esm": "3.2.25", "express": "4.18.1", "handlebars": "4.7.7", "http-shutdown": "1.2.2", @@ -42,8 +42,8 @@ "tileserver-gl-styles": "2.0.0" }, "devDependencies": { + "chai": "4.3.6", "mocha": "^10.0.0", - "should": "^13.2.3", "supertest": "^6.2.4" } } diff --git a/publish.js b/publish.js index 26929c9..7c184f2 100644 --- a/publish.js +++ b/publish.js @@ -16,8 +16,8 @@ require('child_process').execSync('rsync -av --exclude="light" --exclude=".git" }); // PATCH `package.json` -var fs = require('fs'); -var packageJson = require('./package'); +const fs = require('fs'); +const packageJson = require('./package'); packageJson.name += '-light'; packageJson.description = 'Map tile server for JSON GL styles - serving vector tiles'; @@ -30,15 +30,15 @@ delete packageJson.devDependencies; packageJson.engines.node = '>= 10'; -var str = JSON.stringify(packageJson, undefined, 2); +const str = JSON.stringify(packageJson, undefined, 2); fs.writeFileSync('light/package.json', str); fs.renameSync('light/README_light.md', 'light/README.md'); fs.renameSync('light/Dockerfile_light', 'light/Dockerfile'); fs.renameSync('light/docker-entrypoint_light.sh', 'light/docker-entrypoint.sh'); // for Build tileserver-gl-light docker image, don't publish -if (process.argv.length > 2 && process.argv[2] == "--no-publish") { - process.exit(0) +if (process.argv.length > 2 && process.argv[2] == '--no-publish') { + process.exit(0); } /* PUBLISH */ diff --git a/src/main.js b/src/main.js index ea5ef0c..dfc2a4b 100644 --- a/src/main.js +++ b/src/main.js @@ -2,22 +2,24 @@ 'use strict'; -require = require('esm')(module); +import fs from 'node:fs'; +import path from 'path'; +import {fileURLToPath} from 'url'; +import request from 'request'; +import {server} from './server.js'; -const fs = require('fs'); -const path = require('path'); -const request = require('request'); +import MBTiles from '@mapbox/mbtiles'; -const MBTiles = require('@mapbox/mbtiles'); - -const packageJson = require('../package'); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const packageJson = JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8')); const args = process.argv; if (args.length >= 3 && args[2][0] !== '-') { args.splice(2, 0, '--mbtiles'); } -const { program } = require('commander'); +import {program} from 'commander'; program .description('tileserver-gl startup options') .usage('tileserver-gl [mbtiles] [options]') @@ -79,7 +81,7 @@ const startServer = (configPath, config) => { if (publicUrl && publicUrl.lastIndexOf('/') !== publicUrl.length - 1) { publicUrl += '/'; } - return require('./server')({ + return server({ configPath: configPath, config: config, bind: opts.bind, @@ -120,38 +122,37 @@ const startWithMBTiles = (mbtilesFile) => { } const bounds = info.bounds; - const styleDir = path.resolve(__dirname, "../node_modules/tileserver-gl-styles/"); + const styleDir = path.resolve(__dirname, '../node_modules/tileserver-gl-styles/'); const config = { - "options": { - "paths": { - "root": styleDir, - "fonts": "fonts", - "styles": "styles", - "mbtiles": path.dirname(mbtilesFile) + 'options': { + 'paths': { + 'root': styleDir, + 'fonts': 'fonts', + 'styles': 'styles', + 'mbtiles': path.dirname(mbtilesFile) } }, - "styles": {}, - "data": {} + 'styles': {}, + 'data': {} }; if (info.format === 'pbf' && info.name.toLowerCase().indexOf('openmaptiles') > -1) { - config['data'][`v3`] = { - "mbtiles": path.basename(mbtilesFile) + 'mbtiles': path.basename(mbtilesFile) }; const styles = fs.readdirSync(path.resolve(styleDir, 'styles')); - for (let styleName of styles) { + for (const styleName of styles) { const styleFileRel = styleName + '/style.json'; const styleFile = path.resolve(styleDir, 'styles', styleFileRel); if (fs.existsSync(styleFile)) { config['styles'][styleName] = { - "style": styleFileRel, - "tilejson": { - "bounds": bounds + 'style': styleFileRel, + 'tilejson': { + 'bounds': bounds } }; } @@ -159,10 +160,10 @@ const startWithMBTiles = (mbtilesFile) => { } else { console.log(`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`); config['data'][(info.id || 'mbtiles') - .replace(/\//g, '_') - .replace(/:/g, '_') - .replace(/\?/g, '_')] = { - "mbtiles": path.basename(mbtilesFile) + .replace(/\//g, '_') + .replace(/:/g, '_') + .replace(/\?/g, '_')] = { + 'mbtiles': path.basename(mbtilesFile) }; } @@ -183,7 +184,7 @@ fs.stat(path.resolve(opts.config), (err, stats) => { if (!mbtiles) { // try to find in the cwd const files = fs.readdirSync(process.cwd()); - for (let filename of files) { + for (const filename of files) { if (filename.endsWith('.mbtiles')) { const mbTilesStats = fs.statSync(filename); if (mbTilesStats.isFile() && mbTilesStats.size > 0) { diff --git a/src/serve_data.js b/src/serve_data.js index efe0e95..49b666d 100644 --- a/src/serve_data.js +++ b/src/serve_data.js @@ -1,18 +1,18 @@ 'use strict'; -const fs = require('fs'); -const path = require('path'); -const zlib = require('zlib'); +import fs from 'node:fs'; +import path from 'path'; +import zlib from 'zlib'; -const clone = require('clone'); -const express = require('express'); -const MBTiles = require('@mapbox/mbtiles'); -const Pbf = require('pbf'); -const VectorTile = require('@mapbox/vector-tile').VectorTile; +import clone from 'clone'; +import express from 'express'; +import MBTiles from '@mapbox/mbtiles'; +import Pbf from 'pbf'; +import VectorTile from '@mapbox/vector-tile'; -const utils = require('./utils'); +import {getTileUrls, fixTileJSONCenter} from './utils.js'; -module.exports = { +export const serve_data = { init: (options, repo) => { const app = express().disable('x-powered-by'); @@ -21,7 +21,7 @@ module.exports = { if (!item) { return res.sendStatus(404); } - let tileJSONFormat = item.tileJSON.format; + const tileJSONFormat = item.tileJSON.format; const z = req.params.z | 0; const x = req.params.x | 0; const y = req.params.y | 0; @@ -52,7 +52,7 @@ module.exports = { } else { if (tileJSONFormat === 'pbf') { isGzipped = data.slice(0, 2).indexOf( - Buffer.from([0x1f, 0x8b])) === 0; + Buffer.from([0x1f, 0x8b])) === 0; if (options.dataDecoratorFunc) { if (isGzipped) { data = zlib.unzipSync(data); @@ -73,10 +73,10 @@ module.exports = { const tile = new VectorTile(new Pbf(data)); const geojson = { - "type": "FeatureCollection", - "features": [] + 'type': 'FeatureCollection', + 'features': [] }; - for (let layerName in tile.layers) { + for (const layerName in tile.layers) { const layer = tile.layers[layerName]; for (let i = 0; i < layer.length; i++) { const feature = layer.feature(i); @@ -108,10 +108,10 @@ module.exports = { return res.sendStatus(404); } const info = clone(item.tileJSON); - info.tiles = utils.getTileUrls(req, info.tiles, - `data/${req.params.id}`, info.format, item.publicUrl, { - 'pbf': options.pbfAlias - }); + info.tiles = getTileUrls(req, info.tiles, + `data/${req.params.id}`, info.format, item.publicUrl, { + 'pbf': options.pbfAlias + }); return res.send(info); }); @@ -150,7 +150,7 @@ module.exports = { delete tileJSON['scheme']; Object.assign(tileJSON, params.tilejson || {}); - utils.fixTileJSONCenter(tileJSON); + fixTileJSONCenter(tileJSON); if (options.dataDecoratorFunc) { tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON); @@ -165,7 +165,7 @@ module.exports = { tileJSON, publicUrl, source - } + }; }); } }; diff --git a/src/serve_font.js b/src/serve_font.js index c7212e9..5775142 100644 --- a/src/serve_font.js +++ b/src/serve_font.js @@ -1,12 +1,12 @@ 'use strict'; -const express = require('express'); -const fs = require('fs'); -const path = require('path'); +import express from 'express'; +import fs from 'node:fs'; +import path from 'path'; -const utils = require('./utils'); +import {getFontsPbf} from './utils.js'; -module.exports = (options, allowedFonts) => { +export const serve_font = (options, allowedFonts) => { const app = express().disable('x-powered-by'); const lastModified = new Date().toUTCString(); @@ -40,19 +40,19 @@ module.exports = (options, allowedFonts) => { const fontstack = decodeURI(req.params.fontstack); const range = req.params.range; - utils.getFontsPbf(options.serveAllFonts ? null : allowedFonts, - fontPath, fontstack, range, existingFonts).then(concated => { - res.header('Content-type', 'application/x-protobuf'); - res.header('Last-Modified', lastModified); - return res.send(concated); - }, err => res.status(400).send(err) + getFontsPbf(options.serveAllFonts ? null : allowedFonts, + fontPath, fontstack, range, existingFonts).then((concated) => { + res.header('Content-type', 'application/x-protobuf'); + res.header('Last-Modified', lastModified); + return res.send(concated); + }, (err) => res.status(400).send(err) ); }); app.get('/fonts.json', (req, res, next) => { res.header('Content-type', 'application/json'); return res.send( - Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort() + Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort() ); }); diff --git a/src/serve_light.js b/src/serve_light.js new file mode 100644 index 0000000..8505232 --- /dev/null +++ b/src/serve_light.js @@ -0,0 +1,10 @@ +'use strict'; + +export const serve_rendered = { + init: (options, repo) => { + }, + add: (options, repo, params, id, publicUrl, dataResolver) => { + }, + remove: (repo, id) => { + } +}; diff --git a/src/serve_rendered.js b/src/serve_rendered.js index 591e11d..39f15eb 100644 --- a/src/serve_rendered.js +++ b/src/serve_rendered.js @@ -1,37 +1,33 @@ 'use strict'; -const advancedPool = require('advanced-pool'); -const fs = require('fs'); -const path = require('path'); -const url = require('url'); -const util = require('util'); -const zlib = require('zlib'); - -// sharp has to be required before node-canvas -// see https://github.com/lovell/sharp/issues/371 -const sharp = require('sharp'); - -const { createCanvas } = require('canvas'); - -const clone = require('clone'); -const Color = require('color'); -const express = require('express'); -const mercator = new (require('@mapbox/sphericalmercator'))(); -const mbgl = require('@maplibre/maplibre-gl-native'); -const MBTiles = require('@mapbox/mbtiles'); -const proj4 = require('proj4'); -const request = require('request'); - -const utils = require('./utils'); +import advancedPool from 'advanced-pool'; +import fs from 'node:fs'; +import path from 'path'; +import url from 'url'; +import util from 'util'; +import zlib from 'zlib'; +import sharp from 'sharp'; // sharp has to be required before node-canvas. see https://github.com/lovell/sharp/issues/371 +import pkg from 'canvas'; +import clone from 'clone'; +import Color from 'color'; +import express from 'express'; +import SphericalMercator from '@mapbox/sphericalmercator'; +import mlgl from '@maplibre/maplibre-gl-native'; +import MBTiles from '@mapbox/mbtiles'; +import proj4 from 'proj4'; +import request from 'request'; +import {getFontsPbf, getTileUrls, fixTileJSONCenter} from './utils.js'; const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)'; const httpTester = /^(http(s)?:)?\/\//; -const getScale = scale => (scale || '@1x').slice(1, 2) | 0; +const {createCanvas} = pkg; +const mercator = new SphericalMercator(); +const getScale = (scale) => (scale || '@1x').slice(1, 2) | 0; -mbgl.on('message', e => { +mlgl.on('message', (e) => { if (e.severity === 'WARNING' || e.severity === 'ERROR') { - console.log('mbgl:', e); + console.log('mlgl:', e); } }); @@ -54,14 +50,14 @@ const cachedEmptyResponses = { }; /** - * Create an appropriate mbgl response for http errors. + * Create an appropriate mlgl response for http errors. * @param {string} format The format (a sharp format or 'pbf'). * @param {string} color The background color (or empty string for transparent). - * @param {Function} callback The mbgl callback. + * @param {Function} callback The mlgl callback. */ function createEmptyResponse(format, color, callback) { if (!format || format === 'pbf') { - callback(null, { data: cachedEmptyResponses[''] }); + callback(null, {data: cachedEmptyResponses['']}); return; } @@ -75,7 +71,7 @@ function createEmptyResponse(format, color, callback) { const cacheKey = `${format},${color}`; const data = cachedEmptyResponses[cacheKey]; if (data) { - callback(null, { data: data }); + callback(null, {data: data}); return; } @@ -93,7 +89,7 @@ function createEmptyResponse(format, color, callback) { if (!err) { cachedEmptyResponses[cacheKey] = buffer; } - callback(null, { data: buffer }); + callback(null, {data: buffer}); }); } @@ -119,7 +115,7 @@ const extractPathFromQuery = (query, transformer) => { }; const renderOverlay = (z, x, y, bearing, pitch, w, h, scale, - path, query) => { + path, query) => { if (!path || path.length < 2) { return null; } @@ -179,14 +175,14 @@ const calcZForBBox = (bbox, w, h, query) => { const padding = query.padding !== undefined ? parseFloat(query.padding) : 0.1; - const minCorner = mercator.px([bbox[0], bbox[3]], z), - maxCorner = mercator.px([bbox[2], bbox[1]], z); + const minCorner = mercator.px([bbox[0], bbox[3]], z); + const maxCorner = mercator.px([bbox[2], bbox[1]], z); const w_ = w / (1 + 2 * padding); const h_ = h / (1 + 2 * padding); z -= Math.max( - Math.log((maxCorner[0] - minCorner[0]) / w_), - Math.log((maxCorner[1] - minCorner[1]) / h_) + Math.log((maxCorner[0] - minCorner[0]) / w_), + Math.log((maxCorner[1] - minCorner[1]) / h_) ) / Math.LN2; z = Math.max(Math.log(Math.max(w, h) / 256) / Math.LN2, Math.min(25, z)); @@ -197,7 +193,7 @@ const calcZForBBox = (bbox, w, h, query) => { const existingFonts = {}; let maxScaleFactor = 2; -module.exports = { +export const serve_rendered = { init: (options, repo) => { const fontListingPromise = new Promise((resolve, reject) => { fs.readdir(options.paths.fonts, (err, files) => { @@ -253,9 +249,9 @@ module.exports = { pool = item.map.renderers_static[scale]; } pool.acquire((err, renderer) => { - const mbglZ = Math.max(0, z - 1); + const mlglZ = Math.max(0, z - 1); const params = { - zoom: mbglZ, + zoom: mlglZ, center: [lon, lat], bearing: bearing, pitch: pitch, @@ -282,9 +278,9 @@ module.exports = { // Fix semi-transparent outlines on raw, premultiplied input // https://github.com/maptiler/tileserver-gl/issues/350#issuecomment-477857040 - for (var i = 0; i < data.length; i += 4) { - var alpha = data[i + 3]; - var norm = alpha / 255; + for (let i = 0; i < data.length; i += 4) { + const alpha = data[i + 3]; + const norm = alpha / 255; if (alpha === 0) { data[i] = 0; data[i + 1] = 0; @@ -321,7 +317,7 @@ module.exports = { } if (opt_overlay) { - image.composite([{ input: opt_overlay }]); + image.composite([{input: opt_overlay}]); } if (item.watermark) { const canvas = createCanvas(scale * width, scale * height); @@ -334,17 +330,17 @@ module.exports = { ctx.fillStyle = 'rgba(0,0,0,.4)'; ctx.fillText(item.watermark, 5, height - 5); - image.composite([{ input: canvas.toBuffer() }]); + image.composite([{input: canvas.toBuffer()}]); } const formatQuality = (options.formatQuality || {})[format]; if (format === 'png') { - image.png({ adaptiveFiltering: false }); + image.png({adaptiveFiltering: false}); } else if (format === 'jpeg') { - image.jpeg({ quality: formatQuality || 80 }); + image.jpeg({quality: formatQuality || 80}); } else if (format === 'webp') { - image.webp({ quality: formatQuality || 90 }); + image.webp({quality: formatQuality || 90}); } image.toBuffer((err, buffer, info) => { if (!buffer) { @@ -367,18 +363,18 @@ module.exports = { return res.sendStatus(404); } - const modifiedSince = req.get('if-modified-since'), cc = req.get('cache-control'); + const modifiedSince = req.get('if-modified-since'); const cc = req.get('cache-control'); if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) { if (new Date(item.lastModified) <= new Date(modifiedSince)) { return res.sendStatus(304); } } - const z = req.params.z | 0, - x = req.params.x | 0, - y = req.params.y | 0, - scale = getScale(req.params.scale), - format = req.params.format; + const z = req.params.z | 0; + const x = req.params.x | 0; + const y = req.params.y | 0; + const scale = getScale(req.params.scale); + const format = req.params.format; if (z < 0 || x < 0 || y < 0 || z > 22 || x >= Math.pow(2, z) || y >= Math.pow(2, z)) { return res.status(404).send('Out of bounds'); @@ -397,8 +393,8 @@ module.exports = { const centerPattern = util.format(':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?', - FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, - FLOAT_PATTERN, FLOAT_PATTERN); + FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, + FLOAT_PATTERN, FLOAT_PATTERN); app.get(util.format(staticPattern, centerPattern), (req, res, next) => { const item = repo[req.params.id]; @@ -406,15 +402,15 @@ module.exports = { return res.sendStatus(404); } const raw = req.params.raw; - let z = +req.params.z, - x = +req.params.x, - y = +req.params.y, - bearing = +(req.params.bearing || '0'), - pitch = +(req.params.pitch || '0'), - w = req.params.width | 0, - h = req.params.height | 0, - scale = getScale(req.params.scale), - format = req.params.format; + const z = +req.params.z; + let x = +req.params.x; + let y = +req.params.y; + const bearing = +(req.params.bearing || '0'); + const pitch = +(req.params.pitch || '0'); + const w = req.params.width | 0; + const h = req.params.height | 0; + const scale = getScale(req.params.scale); + const format = req.params.format; if (z < 0) { return res.status(404).send('Invalid zoom'); @@ -441,8 +437,7 @@ module.exports = { return res.sendStatus(404); } const raw = req.params.raw; - const bbox = [+req.params.minx, +req.params.miny, - +req.params.maxx, +req.params.maxy]; + const bbox = [+req.params.minx, +req.params.miny, +req.params.maxx, +req.params.maxy]; let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; const transformer = raw ? @@ -458,30 +453,31 @@ module.exports = { center = transformer(center); } - const w = req.params.width | 0, - h = req.params.height | 0, - scale = getScale(req.params.scale), - format = req.params.format; + const w = req.params.width | 0; + const h = req.params.height | 0; + const scale = getScale(req.params.scale); + const format = req.params.format; - const z = calcZForBBox(bbox, w, h, req.query), - x = center[0], - y = center[1], - bearing = 0, - pitch = 0; + const z = calcZForBBox(bbox, w, h, req.query); + const x = center[0]; + const y = center[1]; + const bearing = 0; + const pitch = 0; const path = extractPathFromQuery(req.query, transformer); const overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, path, req.query); + return respondImage(item, z, x, y, bearing, pitch, w, h, scale, format, res, next, overlay, 'static'); }; const boundsPattern = util.format(':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)', - FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN); + FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN); app.get(util.format(staticPattern, boundsPattern), serveBounds); app.get('/:id/static/', (req, res, next) => { - for (let key in req.query) { + for (const key in req.query) { req.query[key.toLowerCase()] = req.query[key]; } req.params.raw = true; @@ -510,12 +506,12 @@ module.exports = { return res.sendStatus(404); } const raw = req.params.raw; - const w = req.params.width | 0, - h = req.params.height | 0, - bearing = 0, - pitch = 0, - scale = getScale(req.params.scale), - format = req.params.format; + const w = req.params.width | 0; + const h = req.params.height | 0; + const bearing = 0; + const pitch = 0; + const scale = getScale(req.params.scale); + const format = req.params.format; const transformer = raw ? mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS; @@ -535,12 +531,12 @@ module.exports = { const bbox_ = mercator.convert(bbox, '900913'); const center = mercator.inverse( - [(bbox_[0] + bbox_[2]) / 2, (bbox_[1] + bbox_[3]) / 2] + [(bbox_[0] + bbox_[2]) / 2, (bbox_[1] + bbox_[3]) / 2] ); - const z = calcZForBBox(bbox, w, h, req.query), - x = center[0], - y = center[1]; + const z = calcZForBBox(bbox, w, h, req.query); + const x = center[0]; + const y = center[1]; const overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, path, req.query); @@ -554,8 +550,8 @@ module.exports = { return res.sendStatus(404); } const info = clone(item.tileJSON); - info.tiles = utils.getTileUrls(req, info.tiles, - `styles/${req.params.id}`, info.format, item.publicUrl); + info.tiles = getTileUrls(req, info.tiles, + `styles/${req.params.id}`, info.format, item.publicUrl); return res.send(info); }); @@ -571,38 +567,38 @@ module.exports = { let styleJSON; const createPool = (ratio, mode, min, max) => { const createRenderer = (ratio, createCallback) => { - const renderer = new mbgl.Map({ + const renderer = new mlgl.Map({ mode: mode, ratio: ratio, request: (req, callback) => { const protocol = req.url.split(':')[0]; - //console.log('Handling request:', req); + // console.log('Handling request:', req); if (protocol === 'sprites') { const dir = options.paths[protocol]; const file = unescape(req.url).substring(protocol.length + 3); fs.readFile(path.join(dir, file), (err, data) => { - callback(err, { data: data }); + callback(err, {data: data}); }); } else if (protocol === 'fonts') { const parts = req.url.split('/'); const fontstack = unescape(parts[2]); const range = parts[3].split('.')[0]; - utils.getFontsPbf( - null, options.paths[protocol], fontstack, range, existingFonts - ).then(concated => { - callback(null, { data: concated }); - }, err => { - callback(err, { data: null }); + getFontsPbf( + null, options.paths[protocol], fontstack, range, existingFonts + ).then((concated) => { + callback(null, {data: concated}); + }, (err) => { + callback(err, {data: null}); }); } else if (protocol === 'mbtiles') { const parts = req.url.split('/'); const sourceId = parts[2]; const source = map.sources[sourceId]; const sourceInfo = styleJSON.sources[sourceId]; - const z = parts[3] | 0, - x = parts[4] | 0, - y = parts[5].split('.')[0] | 0, - format = parts[5].split('.')[1]; + const z = parts[3] | 0; + const x = parts[4] | 0; + const y = parts[5].split('.')[0] | 0; + const format = parts[5].split('.')[1]; source.getTile(z, x, y, (err, data, headers) => { if (err) { if (options.verbose) console.log('MBTiles error, serving empty', err); @@ -619,11 +615,11 @@ module.exports = { try { response.data = zlib.unzipSync(data); } catch (err) { - console.log("Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf", id, z, x, y); + console.log('Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf', id, z, x, y); } if (options.dataDecoratorFunc) { response.data = options.dataDecoratorFunc( - sourceId, 'data', response.data, z, x, y); + sourceId, 'data', response.data, z, x, y); } } else { response.data = data; @@ -670,7 +666,7 @@ module.exports = { min: min, max: max, create: createRenderer.bind(null, ratio), - destroy: renderer => { + destroy: (renderer) => { renderer.release(); } }); @@ -688,8 +684,8 @@ module.exports = { if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) { styleJSON.sprite = 'sprites://' + styleJSON.sprite - .replace('{style}', path.basename(styleFile, '.json')) - .replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleJSONPath))); + .replace('{style}', path.basename(styleFile, '.json')) + .replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleJSONPath))); } if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) { styleJSON.glyphs = `fonts://${styleJSON.glyphs}`; @@ -720,9 +716,9 @@ module.exports = { const attributionOverride = params.tilejson && params.tilejson.attribution; Object.assign(tileJSON, params.tilejson || {}); tileJSON.tiles = params.domains || options.domains; - utils.fixTileJSONCenter(tileJSON); + fixTileJSONCenter(tileJSON); - repo[id] = { + const repoobj = { tileJSON, publicUrl, map, @@ -730,6 +726,7 @@ module.exports = { lastModified: new Date().toUTCString(), watermark: params.watermark || options.watermark }; + repo[id] = repoobj; const queue = []; for (const name of Object.keys(styleJSON.sources)) { @@ -770,11 +767,11 @@ module.exports = { return; } - if (!repo[id].dataProjWGStoInternalWGS && info.proj4) { + if (!repoobj.dataProjWGStoInternalWGS && info.proj4) { // how to do this for multiple sources with different proj4 defs? const to3857 = proj4('EPSG:3857'); const toDataProj = proj4(info.proj4); - repo[id].dataProjWGStoInternalWGS = xy => to3857.inverse(toDataProj.forward(xy)); + repoobj.dataProjWGStoInternalWGS = (xy) => to3857.inverse(toDataProj.forward(xy)); } const type = source.type; @@ -823,15 +820,15 @@ module.exports = { return Promise.all([renderersReadyPromise]); }, remove: (repo, id) => { - let item = repo[id]; + const item = repo[id]; if (item) { - item.map.renderers.forEach(pool => { + item.map.renderers.forEach((pool) => { pool.close(); }); - item.map.renderers_static.forEach(pool => { + item.map.renderers_static.forEach((pool) => { pool.close(); }); } delete repo[id]; - }, + } }; diff --git a/src/serve_style.js b/src/serve_style.js index c336eec..2086691 100644 --- a/src/serve_style.js +++ b/src/serve_style.js @@ -1,13 +1,13 @@ 'use strict'; -const path = require('path'); -const fs = require('fs'); +import path from 'path'; +import fs from 'node:fs'; -const clone = require('clone'); -const express = require('express'); +import clone from 'clone'; +import express from 'express'; import {validate} from '@maplibre/maplibre-gl-style-spec'; -const utils = require('./utils'); +import {getPublicUrl} from './utils.js'; const httpTester = /^(http(s)?:)?\/\//; @@ -24,10 +24,10 @@ const fixUrl = (req, url, publicUrl, opt_nokey) => { query = `?${queryParams.join('&')}`; } return url.replace( - 'local://', utils.getPublicUrl(publicUrl, req)) + query; + 'local://', getPublicUrl(publicUrl, req)) + query; }; -module.exports = { +export const serve_style = { init: (options, repo) => { const app = express().disable('x-powered-by'); @@ -56,8 +56,8 @@ module.exports = { if (!item || !item.spritePath) { return res.sendStatus(404); } - const scale = req.params.scale, - format = req.params.format; + const scale = req.params.scale; + const format = req.params.format; const filename = `${item.spritePath + (scale || '')}.${format}`; return fs.readFile(filename, (err, data) => { if (err) { @@ -87,7 +87,7 @@ module.exports = { return false; } - let validationErrors = validate(styleFileData); + const validationErrors = validate(styleFileData); if (validationErrors.length > 0) { console.log(`The file "${params.style}" is not valid a valid style file:`); for (const err of validationErrors) { @@ -95,7 +95,7 @@ module.exports = { } return false; } - let styleJSON = JSON.parse(styleFileData); + const styleJSON = JSON.parse(styleFileData); for (const name of Object.keys(styleJSON.sources)) { const source = styleJSON.sources[name]; @@ -120,7 +120,7 @@ module.exports = { } } - for (let obj of styleJSON.layers) { + for (const obj of styleJSON.layers) { if (obj['type'] === 'symbol') { const fonts = (obj['layout'] || {})['text-font']; if (fonts && fonts.length) { @@ -136,9 +136,9 @@ module.exports = { if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) { spritePath = path.join(options.paths.sprites, - styleJSON.sprite - .replace('{style}', path.basename(styleFile, '.json')) - .replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleFile))) + styleJSON.sprite + .replace('{style}', path.basename(styleFile, '.json')) + .replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleFile))) ); styleJSON.sprite = `local://styles/${id}/sprite`; } diff --git a/src/server.js b/src/server.js index 9fedc02..3859578 100644 --- a/src/server.js +++ b/src/server.js @@ -1,44 +1,45 @@ #!/usr/bin/env node 'use strict'; +import os from 'os'; process.env.UV_THREADPOOL_SIZE = - Math.ceil(Math.max(4, require('os').cpus().length * 1.5)); + Math.ceil(Math.max(4, os.cpus().length * 1.5)); -const fs = require('fs'); -const path = require('path'); +import fs from 'node:fs'; +import path from 'path'; -const chokidar = require('chokidar'); -const clone = require('clone'); -const cors = require('cors'); -const enableShutdown = require('http-shutdown'); -const express = require('express'); -const handlebars = require('handlebars'); -const mercator = new (require('@mapbox/sphericalmercator'))(); -const morgan = require('morgan'); +import chokidar from 'chokidar'; +import clone from 'clone'; +import cors from 'cors'; +import enableShutdown from 'http-shutdown'; +import express from 'express'; +import handlebars from 'handlebars'; +import SphericalMercator from '@mapbox/sphericalmercator'; +const mercator = new SphericalMercator(); +import morgan from 'morgan'; +import {serve_data} from './serve_data.js'; +import {serve_style} from './serve_style.js'; +import {serve_font} from './serve_font.js'; +import {getTileUrls, getPublicUrl} from './utils.js'; -const packageJson = require('../package'); -const serve_font = require('./serve_font'); -const serve_style = require('./serve_style'); -const serve_data = require('./serve_data'); -const utils = require('./utils'); +import {fileURLToPath} from 'url'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const packageJson = JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8')); -let serve_rendered = null; const isLight = packageJson.name.slice(-6) === '-light'; -if (!isLight) { - // do not require `serve_rendered` in the light package - serve_rendered = require('./serve_rendered'); -} +const serve_rendered = (await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`)).serve_rendered; -function start(opts) { +export function server(opts) { console.log('Starting server'); - const app = express().disable('x-powered-by'), - serving = { - styles: {}, - rendered: {}, - data: {}, - fonts: {} - }; + const app = express().disable('x-powered-by'); + const serving = { + styles: {}, + rendered: {}, + data: {}, + fonts: {} + }; app.enable('trust proxy'); @@ -46,7 +47,7 @@ function start(opts) { const defaultLogFormat = process.env.NODE_ENV === 'production' ? 'tiny' : 'dev'; const logFormat = opts.logFormat || defaultLogFormat; app.use(morgan(logFormat, { - stream: opts.logFile ? fs.createWriteStream(opts.logFile, { flags: 'a' }) : process.stdout, + stream: opts.logFile ? fs.createWriteStream(opts.logFile, {flags: 'a'}) : process.stdout, skip: (req, res) => opts.silent && (res.statusCode === 200 || res.statusCode === 304) })); } @@ -56,7 +57,7 @@ function start(opts) { if (opts.configPath) { configPath = path.resolve(opts.configPath); try { - config = clone(require(configPath)); + config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch (e) { console.log('ERROR: Config file not found or invalid!'); console.log(' See README.md for instructions and sample data.'); @@ -81,7 +82,7 @@ function start(opts) { const startupPromises = []; - const checkPath = type => { + const checkPath = (type) => { if (!fs.existsSync(paths[type])) { console.error(`The specified path for "${type}" does not exist (${paths[type]}).`); process.exit(1); @@ -106,65 +107,65 @@ function start(opts) { app.use('/data/', serve_data.init(options, serving.data)); app.use('/styles/', serve_style.init(options, serving.styles)); - if (serve_rendered) { + if (!isLight) { startupPromises.push( - serve_rendered.init(options, serving.rendered) - .then(sub => { - app.use('/styles/', sub); - }) + serve_rendered.init(options, serving.rendered) + .then((sub) => { + app.use('/styles/', sub); + }) ); } - let addStyle = (id, item, allowMoreData, reportFonts) => { + const addStyle = (id, item, allowMoreData, reportFonts) => { let success = true; if (item.serve_data !== false) { - success = serve_style.add(options, serving.styles, item, id, opts.publicUrl, - (mbtiles, fromData) => { - let dataItemId; - for (const id of Object.keys(data)) { - if (fromData) { - if (id === mbtiles) { - dataItemId = id; - } - } else { - if (data[id].mbtiles === mbtiles) { - dataItemId = id; + success = serve_style.add(options, serving.styles, item, id, opts.publicUrl, + (mbtiles, fromData) => { + let dataItemId; + for (const id of Object.keys(data)) { + if (fromData) { + if (id === mbtiles) { + dataItemId = id; + } + } else { + if (data[id].mbtiles === mbtiles) { + dataItemId = id; + } } } - } - if (dataItemId) { // mbtiles exist in the data config - return dataItemId; - } else { - if (fromData || !allowMoreData) { - console.log(`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`); - return undefined; + if (dataItemId) { // mbtiles exist in the data config + return dataItemId; } else { - let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles; - while (data[id]) id += '_'; - data[id] = { - 'mbtiles': mbtiles - }; - return id; + if (fromData || !allowMoreData) { + console.log(`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`); + return undefined; + } else { + let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles; + while (data[id]) id += '_'; + data[id] = { + 'mbtiles': mbtiles + }; + return id; + } } - } - }, font => { - if (reportFonts) { - serving.fonts[font] = true; - } - }); + }, (font) => { + if (reportFonts) { + serving.fonts[font] = true; + } + }); } if (success && item.serve_rendered !== false) { - if (serve_rendered) { + if (!isLight) { startupPromises.push(serve_rendered.add(options, serving.rendered, item, id, opts.publicUrl, - mbtiles => { - let mbtilesFile; - for (const id of Object.keys(data)) { - if (id === mbtiles) { - mbtilesFile = data[id].mbtiles; + (mbtiles) => { + let mbtilesFile; + for (const id of Object.keys(data)) { + if (id === mbtiles) { + mbtilesFile = data[id].mbtiles; + } } + return mbtilesFile; } - return mbtilesFile; - } )); } else { item.serve_rendered = false; @@ -183,9 +184,9 @@ function start(opts) { } startupPromises.push( - serve_font(options, serving.fonts).then(sub => { - app.use('/', sub); - }) + serve_font(options, serving.fonts).then((sub) => { + app.use('/', sub); + }) ); for (const id of Object.keys(data)) { @@ -196,7 +197,7 @@ function start(opts) { } startupPromises.push( - serve_data.add(options, serving.data, item, id, opts.publicUrl) + serve_data.add(options, serving.data, item, id, opts.publicUrl) ); } @@ -208,8 +209,8 @@ function start(opts) { for (const file of files) { if (file.isFile() && path.extname(file.name).toLowerCase() == '.json') { - let id = path.basename(file.name, '.json'); - let item = { + const id = path.basename(file.name, '.json'); + const item = { style: file.name }; addStyle(id, item, false, false); @@ -218,27 +219,27 @@ function start(opts) { }); const watcher = chokidar.watch(path.join(options.paths.styles, '*.json'), - { - }); + { + }); watcher.on('all', - (eventType, filename) => { - if (filename) { - let id = path.basename(filename, '.json'); - console.log(`Style "${id}" changed, updating...`); + (eventType, filename) => { + if (filename) { + const id = path.basename(filename, '.json'); + console.log(`Style "${id}" changed, updating...`); - serve_style.remove(serving.styles, id); - if (serve_rendered) { - serve_rendered.remove(serving.rendered, id); - } + serve_style.remove(serving.styles, id); + if (!isLight) { + serve_rendered.remove(serving.rendered, id); + } - if (eventType == "add" || eventType == "change") { - let item = { - style: filename - }; - addStyle(id, item, false, false); + if (eventType == 'add' || eventType == 'change') { + const item = { + style: filename + }; + addStyle(id, item, false, false); + } } - } - }); + }); } app.get('/styles.json', (req, res, next) => { @@ -250,7 +251,7 @@ function start(opts) { version: styleJSON.version, name: styleJSON.name, id: id, - url: `${utils.getPublicUrl(opts.publicUrl, req)}styles/${id}/style.json${query}` + url: `${getPublicUrl(opts.publicUrl, req)}styles/${id}/style.json${query}` }); } res.send(result); @@ -265,7 +266,7 @@ function start(opts) { } else { path = `${type}/${id}`; } - info.tiles = utils.getTileUrls(req, info.tiles, path, info.format, opts.publicUrl, { + info.tiles = getTileUrls(req, info.tiles, path, info.format, opts.publicUrl, { 'pbf': options.pbfAlias }); arr.push(info); @@ -283,7 +284,7 @@ function start(opts) { res.send(addTileJSONs(addTileJSONs([], req, 'rendered'), req, 'data')); }); - //------------------------------------ + // ------------------------------------ // serve web presentations app.use('/', express.static(path.join(__dirname, '../public/resources'))); @@ -329,7 +330,7 @@ function start(opts) { })); }; - serveTemplate('/$', 'index', req => { + serveTemplate('/$', 'index', (req) => { const styles = clone(serving.styles || {}); for (const id of Object.keys(styles)) { const style = styles[id]; @@ -345,9 +346,9 @@ function start(opts) { style.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.png`; } - style.xyz_link = utils.getTileUrls( - req, style.serving_rendered.tileJSON.tiles, - `styles/${id}`, style.serving_rendered.tileJSON.format, opts.publicUrl)[0]; + style.xyz_link = getTileUrls( + req, style.serving_rendered.tileJSON.tiles, + `styles/${id}`, style.serving_rendered.tileJSON.format, opts.publicUrl)[0]; } } const data = clone(serving.data || {}); @@ -365,10 +366,10 @@ function start(opts) { data_.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${data_.tileJSON.format}`; } - data_.xyz_link = utils.getTileUrls( - req, tilejson.tiles, `data/${id}`, tilejson.format, opts.publicUrl, { - 'pbf': options.pbfAlias - })[0]; + data_.xyz_link = getTileUrls( + req, tilejson.tiles, `data/${id}`, tilejson.format, opts.publicUrl, { + 'pbf': options.pbfAlias + })[0]; } if (data_.filesize) { let suffix = 'kB'; @@ -390,7 +391,7 @@ function start(opts) { }; }); - serveTemplate('/styles/:id/$', 'viewer', req => { + serveTemplate('/styles/:id/$', 'viewer', (req) => { const id = req.params.id; const style = clone(((serving.styles || {})[id] || {}).styleJSON); if (!style) { @@ -408,13 +409,13 @@ function start(opts) { return res.redirect(301, '/styles/' + req.params.id + '/'); }); */ - serveTemplate('/styles/:id/wmts.xml', 'wmts', req => { + serveTemplate('/styles/:id/wmts.xml', 'wmts', (req) => { const id = req.params.id; const wmts = clone((serving.styles || {})[id]); if (!wmts) { return null; } - if (wmts.hasOwnProperty("serve_rendered") && !wmts.serve_rendered) { + if (wmts.hasOwnProperty('serve_rendered') && !wmts.serve_rendered) { return null; } wmts.id = id; @@ -423,7 +424,7 @@ function start(opts) { return wmts; }); - serveTemplate('/data/:id/$', 'data', req => { + serveTemplate('/data/:id/$', 'data', (req) => { const id = req.params.id; const data = clone(serving.data[id]); if (!data) { @@ -447,7 +448,7 @@ function start(opts) { } }); - const server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function () { + const server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() { let address = this.address().address; if (address.indexOf('::') === 0) { address = `[${address}]`; // literal IPv6 address @@ -465,10 +466,10 @@ function start(opts) { }; } -module.exports = opts => { +export const exports = (opts) => { const running = start(opts); - running.startupPromise.catch(err => { + running.startupPromise.catch((err) => { console.error(err.message); process.exit(1); }); diff --git a/src/utils.js b/src/utils.js index 6037e20..fc6a6d4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,16 +1,15 @@ 'use strict'; -const path = require('path'); -const fs = require('fs'); +import path from 'path'; +import fs from 'node:fs'; -const clone = require('clone'); -const glyphCompose = require('@mapbox/glyph-pbf-composite'); +import clone from 'clone'; +import glyphCompose from '@mapbox/glyph-pbf-composite'; -module.exports.getPublicUrl = (publicUrl, req) => publicUrl || `${req.protocol}://${req.headers.host}/`; - -module.exports.getTileUrls = (req, domains, path, format, publicUrl, aliases) => { +export const getPublicUrl = (publicUrl, req) => publicUrl || `${req.protocol}://${req.headers.host}/`; +export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => { if (domains) { if (domains.constructor === String && domains.length > 0) { domains = domains.split(','); @@ -37,7 +36,6 @@ module.exports.getTileUrls = (req, domains, path, format, publicUrl, aliases) => domains = [req.headers.host]; } - const key = req.query.key; const queryParams = []; if (req.query.key) { queryParams.push(`key=${encodeURIComponent(req.query.key)}`); @@ -57,13 +55,13 @@ module.exports.getTileUrls = (req, domains, path, format, publicUrl, aliases) => uris.push(`${req.protocol}://${domain}/${path}/{z}/{x}/{y}.${format}${query}`); } } else { - uris.push(`${publicUrl}${path}/{z}/{x}/{y}.${format}${query}`) + uris.push(`${publicUrl}${path}/{z}/{x}/{y}.${format}${query}`); } return uris; }; -module.exports.fixTileJSONCenter = tileJSON => { +export const fixTileJSONCenter = (tileJSON) => { if (tileJSON.bounds && !tileJSON.center) { const fitWidth = 1024; const tiles = fitWidth / 256; @@ -71,8 +69,8 @@ module.exports.fixTileJSONCenter = tileJSON => { (tileJSON.bounds[0] + tileJSON.bounds[2]) / 2, (tileJSON.bounds[1] + tileJSON.bounds[3]) / 2, Math.round( - -Math.log((tileJSON.bounds[2] - tileJSON.bounds[0]) / 360 / tiles) / - Math.LN2 + -Math.log((tileJSON.bounds[2] - tileJSON.bounds[0]) / 360 / tiles) / + Math.LN2 ) ]; } @@ -118,14 +116,14 @@ const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) => new Promi } }); -module.exports.getFontsPbf = (allowedFonts, fontPath, names, range, fallbacks) => { +export const getFontsPbf = (allowedFonts, fontPath, names, range, fallbacks) => { const fonts = names.split(','); const queue = []; for (const font of fonts) { queue.push( - getFontPbf(allowedFonts, fontPath, font, range, clone(allowedFonts || fallbacks)) + getFontPbf(allowedFonts, fontPath, font, range, clone(allowedFonts || fallbacks)) ); } - return Promise.all(queue).then(values => glyphCompose.combine(values)); + return Promise.all(queue).then((values) => glyphCompose.combine(values)); }; diff --git a/test/metadata.js b/test/metadata.js index b4594b1..da46cb6 100644 --- a/test/metadata.js +++ b/test/metadata.js @@ -1,38 +1,38 @@ -var testTileJSONArray = function(url) { +const testTileJSONArray = function(url) { describe(url + ' is array of TileJSONs', function() { it('is json', function(done) { supertest(app) - .get(url) - .expect(200) - .expect('Content-Type', /application\/json/, done); + .get(url) + .expect(200) + .expect('Content-Type', /application\/json/, done); }); it('is non-empty array', function(done) { supertest(app) - .get(url) - .expect(function(res) { - res.body.should.be.Array(); - res.body.length.should.be.greaterThan(0); - }).end(done); + .get(url) + .expect(function(res) { + expect(res.body).to.be.a('array'); + expect(res.body.length).to.be.greaterThan(0); + }).end(done); }); }); }; -var testTileJSON = function(url) { +const testTileJSON = function(url) { describe(url + ' is TileJSON', function() { it('is json', function(done) { supertest(app) - .get(url) - .expect(200) - .expect('Content-Type', /application\/json/, done); + .get(url) + .expect(200) + .expect('Content-Type', /application\/json/, done); }); it('has valid tiles', function(done) { supertest(app) - .get(url) - .expect(function(res) { - res.body.tiles.length.should.be.greaterThan(0); - }).end(done); + .get(url) + .expect(function(res) { + expect(res.body.tiles.length).to.be.greaterThan(0); + }).end(done); }); }); }; @@ -41,8 +41,8 @@ describe('Metadata', function() { describe('/health', function() { it('returns 200', function(done) { supertest(app) - .get('/health') - .expect(200, done); + .get('/health') + .expect(200, done); }); }); @@ -53,21 +53,21 @@ describe('Metadata', function() { describe('/styles.json is valid array', function() { it('is json', function(done) { supertest(app) - .get('/styles.json') - .expect(200) - .expect('Content-Type', /application\/json/, done); + .get('/styles.json') + .expect(200) + .expect('Content-Type', /application\/json/, done); }); it('contains valid item', function(done) { supertest(app) - .get('/styles.json') - .expect(function(res) { - res.body.should.be.Array(); - res.body.length.should.be.greaterThan(0); - res.body[0].version.should.equal(8); - res.body[0].id.should.be.String(); - res.body[0].name.should.be.String(); - }).end(done); + .get('/styles.json') + .expect(function(res) { + expect(res.body).to.be.a('array'); + expect(res.body.length).to.be.greaterThan(0); + expect(res.body[0].version).to.be.equal(8); + expect(res.body[0].id).to.be.a('string'); + expect(res.body[0].name).to.be.a('string'); + }).end(done); }); }); diff --git a/test/setup.js b/test/setup.js index 4e8d0a0..a97cc85 100644 --- a/test/setup.js +++ b/test/setup.js @@ -1,14 +1,16 @@ process.env.NODE_ENV = 'test'; -global.should = require('should'); -global.supertest = require('supertest'); +import {expect} from 'chai'; +import supertest from 'supertest'; +import {server} from '../src/server.js'; -require = require('esm')(module); +global.expect = expect; +global.supertest = supertest; before(function() { console.log('global setup'); process.chdir('test_data'); - var running = require('../src/server')({ + const running = server({ configPath: 'config.json', port: 8888, publicUrl: '/test/' @@ -20,5 +22,7 @@ before(function() { after(function() { console.log('global teardown'); - global.server.close(function() { console.log('Done'); process.exit(); }); + global.server.close(function() { + console.log('Done'); process.exit(); + }); }); diff --git a/test/static.js b/test/static.js index fc3b59b..9499034 100644 --- a/test/static.js +++ b/test/static.js @@ -1,18 +1,18 @@ -var testStatic = function(prefix, q, format, status, scale, type, query) { +const testStatic = function(prefix, q, format, status, scale, type, query) { if (scale) q += '@' + scale + 'x'; - var path = '/styles/' + prefix + '/static/' + q + '.' + format; + let path = '/styles/' + prefix + '/static/' + q + '.' + format; if (query) { path += query; } it(path + ' returns ' + status, function(done) { - var test = supertest(app).get(path); + const test = supertest(app).get(path); if (status) test.expect(status); if (type) test.expect('Content-Type', type); test.end(done); }); }; -var prefix = 'test-style'; +const prefix = 'test-style'; describe('Static endpoints', function() { describe('center-based', function() { diff --git a/test/style.js b/test/style.js index 938798d..f7ab30a 100644 --- a/test/style.js +++ b/test/style.js @@ -1,14 +1,14 @@ -var testIs = function(url, type, status) { +const testIs = function(url, type, status) { it(url + ' return ' + (status || 200) + ' and is ' + type.toString(), function(done) { - supertest(app) - .get(url) - .expect(status || 200) - .expect('Content-Type', type, done); - }); + supertest(app) + .get(url) + .expect(status || 200) + .expect('Content-Type', type, done); + }); }; -var prefix = 'test-style'; +const prefix = 'test-style'; describe('Styles', function() { describe('/styles/' + prefix + '/style.json is valid style', function() { @@ -16,16 +16,16 @@ describe('Styles', function() { it('contains expected properties', function(done) { supertest(app) - .get('/styles/' + prefix + '/style.json') - .expect(function(res) { - res.body.version.should.equal(8); - res.body.name.should.be.String(); - res.body.sources.should.be.Object(); - res.body.glyphs.should.be.String(); - res.body.sprite.should.be.String(); - res.body.sprite.should.equal('/test/styles/test-style/sprite'); - res.body.layers.should.be.Array(); - }).end(done); + .get('/styles/' + prefix + '/style.json') + .expect(function(res) { + expect(res.body.version).to.be.equal(8); + expect(res.body.name).to.be.a('string'); + expect(res.body.sources).to.be.a('object'); + expect(res.body.glyphs).to.be.a('string'); + expect(res.body.sprite).to.be.a('string'); + expect(res.body.sprite).to.be.equal('/test/styles/test-style/sprite'); + expect(res.body.layers).to.be.a('array'); + }).end(done); }); }); describe('/styles/streets/style.json is not served', function() { @@ -44,7 +44,7 @@ describe('Fonts', function() { testIs('/fonts/Open Sans Bold/0-255.pbf', /application\/x-protobuf/); testIs('/fonts/Open Sans Regular/65280-65535.pbf', /application\/x-protobuf/); testIs('/fonts/Open Sans Bold,Open Sans Regular/0-255.pbf', - /application\/x-protobuf/); + /application\/x-protobuf/); testIs('/fonts/Nonsense,Open Sans Bold/0-255.pbf', /./, 400); testIs('/fonts/Nonsense/0-255.pbf', /./, 400); diff --git a/test/tiles_data.js b/test/tiles_data.js index 5b9b68e..310a1e8 100644 --- a/test/tiles_data.js +++ b/test/tiles_data.js @@ -1,14 +1,14 @@ -var testTile = function(prefix, z, x, y, status) { - var path = '/data/' + prefix + '/' + z + '/' + x + '/' + y + '.pbf'; +const testTile = function(prefix, z, x, y, status) { + const path = '/data/' + prefix + '/' + z + '/' + x + '/' + y + '.pbf'; it(path + ' returns ' + status, function(done) { - var test = supertest(app).get(path); + const test = supertest(app).get(path); if (status) test.expect(status); if (status == 200) test.expect('Content-Type', /application\/x-protobuf/); test.end(done); }); }; -var prefix = 'openmaptiles'; +const prefix = 'openmaptiles'; describe('Vector tiles', function() { describe('existing tiles', function() { diff --git a/test/tiles_rendered.js b/test/tiles_rendered.js index 5547f52..73e8c16 100644 --- a/test/tiles_rendered.js +++ b/test/tiles_rendered.js @@ -1,15 +1,15 @@ -var testTile = function(prefix, z, x, y, format, status, scale, type) { +const testTile = function(prefix, z, x, y, format, status, scale, type) { if (scale) y += '@' + scale + 'x'; - var path = '/styles/' + prefix + '/' + z + '/' + x + '/' + y + '.' + format; + const path = '/styles/' + prefix + '/' + z + '/' + x + '/' + y + '.' + format; it(path + ' returns ' + status, function(done) { - var test = supertest(app).get(path); + const test = supertest(app).get(path); test.expect(status); if (type) test.expect('Content-Type', type); test.end(done); }); }; -var prefix = 'test-style'; +const prefix = 'test-style'; describe('Raster tiles', function() { describe('valid requests', function() { @@ -41,6 +41,6 @@ describe('Raster tiles', function() { testTile(prefix, 0, 0, 0, 'png', 404, 1); testTile(prefix, 0, 0, 0, 'png', 404, 5); - //testTile('hybrid', 0, 0, 0, 'png', 404); //TODO: test this + // testTile('hybrid', 0, 0, 0, 'png', 404); //TODO: test this }); });