Merge branch 'master' into windows

Signed-off-by: acalcutt <acalcutt@techidiots.net>
This commit is contained in:
acalcutt 2023-11-27 11:05:56 -05:00
commit c634cdf0b3
9 changed files with 219 additions and 314 deletions

View file

@ -79,7 +79,7 @@
<div class="details"> <div class="details">
<h3>{{tileJSON.name}}</h3> <h3>{{tileJSON.name}}</h3>
<div class="identifier">identifier: {{@key}}{{#if formatted_filesize}} | size: {{formatted_filesize}}{{/if}}</div> <div class="identifier">identifier: {{@key}}{{#if formatted_filesize}} | size: {{formatted_filesize}}{{/if}}</div>
<div class="identifier">type: {{#is_vector}}vector{{/is_vector}}{{^is_vector}}raster{{/is_vector}} data {{#if source_type}} | ext: {{source_type}}{{/if}}</div> <div class="identifier">type: {{#is_vector}}vector{{/is_vector}}{{^is_vector}}raster{{/is_vector}} data {{#if sourceType}} | ext: {{sourceType}}{{/if}}</div>
<p class="services"> <p class="services">
services: <a href="{{public_url}}data/{{@key}}.json{{&../key_query}}">TileJSON</a> services: <a href="{{public_url}}data/{{@key}}.json{{&../key_query}}">TileJSON</a>
{{#if xyz_link}} {{#if xyz_link}}

View file

@ -9,7 +9,7 @@ import axios from 'axios';
import { server } from './server.js'; import { server } from './server.js';
import MBTiles from '@mapbox/mbtiles'; import MBTiles from '@mapbox/mbtiles';
import { isValidHttpUrl } from './utils.js'; import { isValidHttpUrl } from './utils.js';
import { PMtilesOpen, GetPMtilesInfo } from './pmtiles_adapter.js'; import { openPMtiles, getPMtilesInfo } from './pmtiles_adapter.js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@ -62,14 +62,14 @@ const opts = program.opts();
console.log(`Starting ${packageJson.name} v${packageJson.version}`); console.log(`Starting ${packageJson.name} v${packageJson.version}`);
const StartServer = (configPath, config) => { const startServer = (configPath, config) => {
let publicUrl = opts.public_url; let publicUrl = opts.public_url;
if (publicUrl && publicUrl.lastIndexOf('/') !== publicUrl.length - 1) { if (publicUrl && publicUrl.lastIndexOf('/') !== publicUrl.length - 1) {
publicUrl += '/'; publicUrl += '/';
} }
return server({ return server({
configPath: configPath, configPath,
config: config, config,
bind: opts.bind, bind: opts.bind,
port: opts.port, port: opts.port,
cors: opts.cors, cors: opts.cors,
@ -77,11 +77,11 @@ const StartServer = (configPath, config) => {
silent: opts.silent, silent: opts.silent,
logFile: opts.log_file, logFile: opts.log_file,
logFormat: opts.log_format, logFormat: opts.log_format,
publicUrl: publicUrl, publicUrl,
}); });
}; };
const StartWithInputFile = async (inputFile) => { const startWithInputFile = async (inputFile) => {
console.log(`[INFO] Automatically creating config file for ${inputFile}`); console.log(`[INFO] Automatically creating config file for ${inputFile}`);
console.log(`[INFO] Only a basic preview style will be used.`); console.log(`[INFO] Only a basic preview style will be used.`);
console.log( console.log(
@ -123,8 +123,8 @@ const StartWithInputFile = async (inputFile) => {
const extension = inputFile.split('.').pop().toLowerCase(); const extension = inputFile.split('.').pop().toLowerCase();
if (extension === 'pmtiles') { if (extension === 'pmtiles') {
let FileOpenInfo = PMtilesOpen(inputFile); const fileOpenInfo = openPMtiles(inputFile);
const metadata = await GetPMtilesInfo(FileOpenInfo); const metadata = await getPMtilesInfo(fileOpenInfo);
if ( if (
metadata.format === 'pbf' && metadata.format === 'pbf' &&
@ -174,7 +174,7 @@ const StartWithInputFile = async (inputFile) => {
console.log('Run with --verbose to see the config file here.'); console.log('Run with --verbose to see the config file here.');
} }
return StartServer(null, config); return startServer(null, config);
} else { } else {
if (isValidHttpUrl(inputFile)) { if (isValidHttpUrl(inputFile)) {
console.log( console.log(
@ -215,7 +215,7 @@ const StartWithInputFile = async (inputFile) => {
config['styles'][styleName] = { config['styles'][styleName] = {
style: styleFileRel, style: styleFileRel,
tilejson: { tilejson: {
bounds: bounds, bounds,
}, },
}; };
} }
@ -235,13 +235,13 @@ const StartWithInputFile = async (inputFile) => {
console.log('Run with --verbose to see the config file here.'); console.log('Run with --verbose to see the config file here.');
} }
return StartServer(null, config); return startServer(null, config);
}); });
}); });
} }
}; };
fs.stat(path.resolve(opts.config), (err, stats) => { fs.stat(path.resolve(opts.config), async (err, stats) => {
if (err || !stats.isFile() || stats.size === 0) { if (err || !stats.isFile() || stats.size === 0) {
let inputFile; let inputFile;
if (opts.file) { if (opts.file) {
@ -251,7 +251,7 @@ fs.stat(path.resolve(opts.config), (err, stats) => {
} }
if (inputFile) { if (inputFile) {
return StartWithInputFile(inputFile); return startWithInputFile(inputFile);
} else { } else {
// try to find in the cwd // try to find in the cwd
const files = fs.readdirSync(process.cwd()); const files = fs.readdirSync(process.cwd());
@ -266,7 +266,7 @@ fs.stat(path.resolve(opts.config), (err, stats) => {
} }
if (inputFile) { if (inputFile) {
console.log(`No input file specified, using ${inputFile}`); console.log(`No input file specified, using ${inputFile}`);
return StartWithInputFile(inputFile); return startWithInputFile(inputFile);
} else { } else {
const url = const url =
'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles'; 'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
@ -274,25 +274,26 @@ fs.stat(path.resolve(opts.config), (err, stats) => {
const writer = fs.createWriteStream(filename); const writer = fs.createWriteStream(filename);
console.log(`No input file found`); console.log(`No input file found`);
console.log(`[DEMO] Downloading sample data (${filename}) from ${url}`); console.log(`[DEMO] Downloading sample data (${filename}) from ${url}`);
axios({
try {
const response = await axios({
url, url,
method: 'GET', method: 'GET',
responseType: 'stream', responseType: 'stream',
}) });
.then((response) => {
response.data.pipe(writer); response.data.pipe(writer);
writer.on('finish', () => StartWithInputFile(filename)); writer.on('finish', () => startWithInputFile(filename));
writer.on('error', (err) => writer.on('error', (err) =>
console.error(`Error writing file: ${err}`), console.error(`Error writing file: ${err}`),
); );
}) } catch (error) {
.catch((error) => {
console.error(`Error downloading file: ${error}`); console.error(`Error downloading file: ${error}`);
}); }
} }
} }
} else { } else {
console.log(`Using specified config file from ${opts.config}`); console.log(`Using specified config file from ${opts.config}`);
return StartServer(opts.config, null); return startServer(opts.config, null);
} }
}); });

View file

@ -11,7 +11,7 @@ class PMTilesFileSource {
} }
async getBytes(offset, length) { async getBytes(offset, length) {
const buffer = Buffer.alloc(length); const buffer = Buffer.alloc(length);
await ReadFileBytes(this.fd, buffer, offset); await readFileBytes(this.fd, buffer, offset);
const ab = buffer.buffer.slice( const ab = buffer.buffer.slice(
buffer.byteOffset, buffer.byteOffset,
buffer.byteOffset + buffer.byteLength, buffer.byteOffset + buffer.byteLength,
@ -26,7 +26,7 @@ class PMTilesFileSource {
* @param buffer * @param buffer
* @param offset * @param offset
*/ */
async function ReadFileBytes(fd, buffer, offset) { async function readFileBytes(fd, buffer, offset) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.read(fd, buffer, 0, buffer.length, offset, (err) => { fs.read(fd, buffer, 0, buffer.length, offset, (err) => {
if (err) { if (err) {
@ -41,7 +41,7 @@ async function ReadFileBytes(fd, buffer, offset) {
* *
* @param FilePath * @param FilePath
*/ */
export function PMtilesOpen(FilePath) { export function openPMtiles(FilePath) {
let pmtiles = undefined; let pmtiles = undefined;
if (isValidHttpUrl(FilePath)) { if (isValidHttpUrl(FilePath)) {
@ -59,12 +59,12 @@ export function PMtilesOpen(FilePath) {
* *
* @param pmtiles * @param pmtiles
*/ */
export async function GetPMtilesInfo(pmtiles) { export async function getPMtilesInfo(pmtiles) {
const header = await pmtiles.getHeader(); const header = await pmtiles.getHeader();
const metadata = await pmtiles.getMetadata(); const metadata = await pmtiles.getMetadata();
//Add missing metadata from header //Add missing metadata from header
metadata['format'] = GetPmtilesTileType(header.tileType).type; metadata['format'] = getPmtilesTileType(header.tileType).type;
metadata['minzoom'] = header.minZoom; metadata['minzoom'] = header.minZoom;
metadata['maxzoom'] = header.maxZoom; metadata['maxzoom'] = header.maxZoom;
@ -103,23 +103,23 @@ export async function GetPMtilesInfo(pmtiles) {
* @param x * @param x
* @param y * @param y
*/ */
export async function GetPMtilesTile(pmtiles, z, x, y) { export async function getPMtilesTile(pmtiles, z, x, y) {
const header = await pmtiles.getHeader(); const header = await pmtiles.getHeader();
const TileType = GetPmtilesTileType(header.tileType); const tileType = getPmtilesTileType(header.tileType);
let zxyTile = await pmtiles.getZxy(z, x, y); let zxyTile = await pmtiles.getZxy(z, x, y);
if (zxyTile && zxyTile.data) { if (zxyTile && zxyTile.data) {
zxyTile = Buffer.from(zxyTile.data); zxyTile = Buffer.from(zxyTile.data);
} else { } else {
zxyTile = undefined; zxyTile = undefined;
} }
return { data: zxyTile, header: TileType.header }; return { data: zxyTile, header: tileType.header };
} }
/** /**
* *
* @param typenum * @param typenum
*/ */
function GetPmtilesTileType(typenum) { function getPmtilesTileType(typenum) {
let head = {}; let head = {};
let tileType; let tileType;
switch (typenum) { switch (typenum) {

View file

@ -12,9 +12,9 @@ import { VectorTile } from '@mapbox/vector-tile';
import { getTileUrls, isValidHttpUrl, fixTileJSONCenter } from './utils.js'; import { getTileUrls, isValidHttpUrl, fixTileJSONCenter } from './utils.js';
import { import {
PMtilesOpen, openPMtiles,
GetPMtilesInfo, getPMtilesInfo,
GetPMtilesTile, getPMtilesTile,
} from './pmtiles_adapter.js'; } from './pmtiles_adapter.js';
export const serve_data = { export const serve_data = {
@ -53,8 +53,8 @@ export const serve_data = {
) { ) {
return res.status(404).send('Out of bounds'); return res.status(404).send('Out of bounds');
} }
if (item.source_type === 'pmtiles') { if (item.sourceType === 'pmtiles') {
let tileinfo = await GetPMtilesTile(item.source, z, x, y); let tileinfo = await getPMtilesTile(item.source, z, x, y);
if (tileinfo == undefined || tileinfo.data == undefined) { if (tileinfo == undefined || tileinfo.data == undefined) {
return res.status(404).send('Not found'); return res.status(404).send('Not found');
} else { } else {
@ -99,7 +99,7 @@ export const serve_data = {
return res.status(200).send(data); return res.status(200).send(data);
} }
} else if (item.source_type === 'mbtiles') { } else if (item.sourceType === 'mbtiles') {
item.source.getTile(z, x, y, (err, data, headers) => { item.source.getTile(z, x, y, (err, data, headers) => {
let isGzipped; let isGzipped;
if (err) { if (err) {
@ -223,11 +223,11 @@ export const serve_data = {
} }
let source; let source;
let source_type; let sourceType;
if (inputType === 'pmtiles') { if (inputType === 'pmtiles') {
source = PMtilesOpen(inputFile); source = openPMtiles(inputFile);
source_type = 'pmtiles'; sourceType = 'pmtiles';
const metadata = await GetPMtilesInfo(source); const metadata = await getPMtilesInfo(source);
tileJSON['name'] = id; tileJSON['name'] = id;
tileJSON['format'] = 'pbf'; tileJSON['format'] = 'pbf';
@ -245,7 +245,7 @@ export const serve_data = {
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON); tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
} }
} else if (inputType === 'mbtiles') { } else if (inputType === 'mbtiles') {
source_type = 'mbtiles'; sourceType = 'mbtiles';
const sourceInfoPromise = new Promise((resolve, reject) => { const sourceInfoPromise = new Promise((resolve, reject) => {
source = new MBTiles(inputFile + '?mode=ro', (err) => { source = new MBTiles(inputFile + '?mode=ro', (err) => {
if (err) { if (err) {
@ -285,7 +285,7 @@ export const serve_data = {
tileJSON, tileJSON,
publicUrl, publicUrl,
source, source,
source_type, sourceType,
}; };
}, },
}; };

View file

@ -4,7 +4,7 @@ import express from 'express';
import { getFontsPbf, listFonts } from './utils.js'; import { getFontsPbf, listFonts } from './utils.js';
export const serve_font = (options, allowedFonts) => { export const serve_font = async (options, allowedFonts) => {
const app = express().disable('x-powered-by'); const app = express().disable('x-powered-by');
const lastModified = new Date().toUTCString(); const lastModified = new Date().toUTCString();
@ -13,25 +13,29 @@ export const serve_font = (options, allowedFonts) => {
const existingFonts = {}; const existingFonts = {};
app.get('/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf', (req, res, next) => { app.get(
'/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf',
async (req, res, next) => {
const fontstack = decodeURI(req.params.fontstack); const fontstack = decodeURI(req.params.fontstack);
const range = req.params.range; const range = req.params.range;
getFontsPbf( try {
const concatenated = await getFontsPbf(
options.serveAllFonts ? null : allowedFonts, options.serveAllFonts ? null : allowedFonts,
fontPath, fontPath,
fontstack, fontstack,
range, range,
existingFonts, existingFonts,
).then( );
(concated) => {
res.header('Content-type', 'application/x-protobuf'); res.header('Content-type', 'application/x-protobuf');
res.header('Last-Modified', lastModified); res.header('Last-Modified', lastModified);
return res.send(concated); return res.send(concatenated);
} catch (err) {
res.status(400).header('Content-Type', 'text/plain').send(err);
}
}, },
(err) => res.status(400).header('Content-Type', 'text/plain').send(err),
); );
});
app.get('/fonts.json', (req, res, next) => { app.get('/fonts.json', (req, res, next) => {
res.header('Content-type', 'application/json'); res.header('Content-type', 'application/json');
@ -40,8 +44,7 @@ export const serve_font = (options, allowedFonts) => {
); );
}); });
return listFonts(options.paths.fonts).then((fonts) => { const fonts = await listFonts(options.paths.fonts);
Object.assign(existingFonts, fonts); Object.assign(existingFonts, fonts);
return app; return app;
});
}; };

View file

@ -1,13 +1,24 @@
'use strict'; 'use strict';
// SECTION START
//
// The order of the two imports below is important.
// For an unknown reason, if the order is reversed, rendering can crash.
// This happens on ARM:
// > terminate called after throwing an instance of 'std::runtime_error'
// > what(): Cannot read GLX extensions.
import 'canvas';
import '@maplibre/maplibre-gl-native';
//
// SECTION END
import advancedPool from 'advanced-pool'; import advancedPool from 'advanced-pool';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'path'; import path from 'path';
import url from 'url'; import url from 'url';
import util from 'util'; import util from 'util';
import zlib from 'zlib'; import zlib from 'zlib';
import { renderOverlay, renderWatermark, renderAttribution } from './render.js'; import sharp from 'sharp';
import sharp from 'sharp'; // sharp has to be required before node-canvas on linux but after it on windows. see https://github.com/lovell/sharp/issues/371
import clone from 'clone'; import clone from 'clone';
import Color from 'color'; import Color from 'color';
import express from 'express'; import express from 'express';
@ -26,10 +37,11 @@ import {
fixTileJSONCenter, fixTileJSONCenter,
} from './utils.js'; } from './utils.js';
import { import {
PMtilesOpen, openPMtiles,
GetPMtilesInfo, getPMtilesInfo,
GetPMtilesTile, getPMtilesTile,
} from './pmtiles_adapter.js'; } from './pmtiles_adapter.js';
import { renderOverlay, renderWatermark, renderAttribution } from './render.js';
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)'; const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
const PATH_PATTERN = const PATH_PATTERN =
@ -97,7 +109,7 @@ function createEmptyResponse(format, color, callback) {
raw: { raw: {
width: 1, width: 1,
height: 1, height: 1,
channels: channels, channels,
}, },
}) })
.toFormat(format) .toFormat(format)
@ -398,17 +410,17 @@ const respondImage = (
if (mode === 'tile' && tileMargin === 0) { if (mode === 'tile' && tileMargin === 0) {
pool = item.map.renderers[scale]; pool = item.map.renderers[scale];
} else { } else {
pool = item.map.renderers_static[scale]; pool = item.map.renderersStatic[scale];
} }
pool.acquire((err, renderer) => { pool.acquire((err, renderer) => {
const mlglZ = Math.max(0, z - 1); const mlglZ = Math.max(0, z - 1);
const params = { const params = {
zoom: mlglZ, zoom: mlglZ,
center: [lon, lat], center: [lon, lat],
bearing: bearing, bearing,
pitch: pitch, pitch,
width: width, width,
height: height, height,
}; };
if (z === 0) { if (z === 0) {
@ -428,24 +440,9 @@ const respondImage = (
return res.status(500).header('Content-Type', 'text/plain').send(err); return res.status(500).header('Content-Type', 'text/plain').send(err);
} }
// Fix semi-transparent outlines on raw, premultiplied input
// https://github.com/maptiler/tileserver-gl/issues/350#issuecomment-477857040
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;
data[i + 2] = 0;
} else {
data[i] = data[i] / norm;
data[i + 1] = data[i + 1] / norm;
data[i + 2] = data[i + 2] / norm;
}
}
const image = sharp(data, { const image = sharp(data, {
raw: { raw: {
premultiplied: true,
width: params.width * scale, width: params.width * scale,
height: params.height * scale, height: params.height * scale,
channels: 4, channels: 4,
@ -471,14 +468,14 @@ const respondImage = (
image.resize(width * scale, height * scale); image.resize(width * scale, height * scale);
} }
const composite_array = []; const composites = [];
if (overlay) { if (overlay) {
composite_array.push({ input: overlay }); composites.push({ input: overlay });
} }
if (item.watermark) { if (item.watermark) {
const canvas = renderWatermark(width, height, scale, item.watermark); const canvas = renderWatermark(width, height, scale, item.watermark);
composite_array.push({ input: canvas.toBuffer() }); composites.push({ input: canvas.toBuffer() });
} }
if (mode === 'static' && item.staticAttributionText) { if (mode === 'static' && item.staticAttributionText) {
@ -489,11 +486,11 @@ const respondImage = (
item.staticAttributionText, item.staticAttributionText,
); );
composite_array.push({ input: canvas.toBuffer() }); composites.push({ input: canvas.toBuffer() });
} }
if (composite_array.length > 0) { if (composites.length > 0) {
image.composite(composite_array); image.composite(composites);
} }
const formatQuality = (options.formatQuality || {})[format]; const formatQuality = (options.formatQuality || {})[format];
@ -524,7 +521,7 @@ const existingFonts = {};
let maxScaleFactor = 2; let maxScaleFactor = 2;
export const serve_rendered = { export const serve_rendered = {
init: (options, repo) => { init: async (options, repo) => {
maxScaleFactor = Math.min(Math.floor(options.maxScaleFactor || 3), 9); maxScaleFactor = Math.min(Math.floor(options.maxScaleFactor || 3), 9);
let scalePattern = ''; let scalePattern = '';
for (let i = 2; i <= maxScaleFactor; i++) { for (let i = 2; i <= maxScaleFactor; i++) {
@ -573,19 +570,10 @@ export const serve_rendered = {
], ],
z, z,
); );
// prettier-ignore
return respondImage( return respondImage(
options, options, item, z, tileCenter[0], tileCenter[1], 0, 0, tileSize, tileSize, scale, format, res,
item,
z,
tileCenter[0],
tileCenter[1],
0,
0,
tileSize,
tileSize,
scale,
format,
res,
); );
}, },
); );
@ -641,35 +629,15 @@ export const serve_rendered = {
options, options,
transformer, transformer,
); );
// prettier-ignore
const overlay = await renderOverlay( const overlay = await renderOverlay(
z, z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query,
x,
y,
bearing,
pitch,
w,
h,
scale,
paths,
markers,
req.query,
); );
// prettier-ignore
return respondImage( return respondImage(
options, options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static',
item,
z,
x,
y,
bearing,
pitch,
w,
h,
scale,
format,
res,
overlay,
'static',
); );
} catch (e) { } catch (e) {
next(e); next(e);
@ -723,34 +691,15 @@ export const serve_rendered = {
options, options,
transformer, transformer,
); );
// prettier-ignore
const overlay = await renderOverlay( const overlay = await renderOverlay(
z, z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query,
x,
y,
bearing,
pitch,
w,
h,
scale,
paths,
markers,
req.query,
); );
// prettier-ignore
return respondImage( return respondImage(
options, options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static',
item,
z,
x,
y,
bearing,
pitch,
w,
h,
scale,
format,
res,
overlay,
'static',
); );
} catch (e) { } catch (e) {
next(e); next(e);
@ -856,35 +805,14 @@ export const serve_rendered = {
const x = center[0]; const x = center[0];
const y = center[1]; const y = center[1];
// prettier-ignore
const overlay = await renderOverlay( const overlay = await renderOverlay(
z, z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query,
x,
y,
bearing,
pitch,
w,
h,
scale,
paths,
markers,
req.query,
); );
// prettier-ignore
return respondImage( return respondImage(
options, options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static',
item,
z,
x,
y,
bearing,
pitch,
w,
h,
scale,
format,
res,
overlay,
'static',
); );
} catch (e) { } catch (e) {
next(e); next(e);
@ -909,25 +837,24 @@ export const serve_rendered = {
return res.send(info); return res.send(info);
}); });
return listFonts(options.paths.fonts).then((fonts) => { const fonts = await listFonts(options.paths.fonts);
Object.assign(existingFonts, fonts); Object.assign(existingFonts, fonts);
return app; return app;
});
}, },
add: async (options, repo, params, id, publicUrl, dataResolver) => { add: async (options, repo, params, id, publicUrl, dataResolver) => {
const map = { const map = {
renderers: [], renderers: [],
renderers_static: [], renderersStatic: [],
sources: {}, sources: {},
source_types: {}, sourceTypes: {},
}; };
let styleJSON; let styleJSON;
const createPool = (ratio, mode, min, max) => { const createPool = (ratio, mode, min, max) => {
const createRenderer = (ratio, createCallback) => { const createRenderer = (ratio, createCallback) => {
const renderer = new mlgl.Map({ const renderer = new mlgl.Map({
mode: mode, mode,
ratio: ratio, ratio,
request: async (req, callback) => { request: async (req, callback) => {
const protocol = req.url.split(':')[0]; const protocol = req.url.split(':')[0];
// console.log('Handling request:', req); // console.log('Handling request:', req);
@ -941,25 +868,24 @@ export const serve_rendered = {
const parts = req.url.split('/'); const parts = req.url.split('/');
const fontstack = unescape(parts[2]); const fontstack = unescape(parts[2]);
const range = parts[3].split('.')[0]; const range = parts[3].split('.')[0];
getFontsPbf(
try {
const concatenated = await getFontsPbf(
null, null,
options.paths[protocol], options.paths[protocol],
fontstack, fontstack,
range, range,
existingFonts, existingFonts,
).then(
(concated) => {
callback(null, { data: concated });
},
(err) => {
callback(err, { data: null });
},
); );
callback(null, { data: concatenated });
} catch (err) {
callback(err, { data: null });
}
} else if (protocol === 'mbtiles' || protocol === 'pmtiles') { } else if (protocol === 'mbtiles' || protocol === 'pmtiles') {
const parts = req.url.split('/'); const parts = req.url.split('/');
const sourceId = parts[2]; const sourceId = parts[2];
const source = map.sources[sourceId]; const source = map.sources[sourceId];
const source_type = map.source_types[sourceId]; const sourceType = map.sourceTypes[sourceId];
const sourceInfo = styleJSON.sources[sourceId]; const sourceInfo = styleJSON.sources[sourceId];
const z = parts[3] | 0; const z = parts[3] | 0;
@ -967,8 +893,8 @@ export const serve_rendered = {
const y = parts[5].split('.')[0] | 0; const y = parts[5].split('.')[0] | 0;
const format = parts[5].split('.')[1]; const format = parts[5].split('.')[1];
if (source_type === 'pmtiles') { if (sourceType === 'pmtiles') {
let tileinfo = await GetPMtilesTile(source, z, x, y); let tileinfo = await getPMtilesTile(source, z, x, y);
let data = tileinfo.data; let data = tileinfo.data;
let headers = tileinfo.header; let headers = tileinfo.header;
if (data == undefined) { if (data == undefined) {
@ -1002,7 +928,7 @@ export const serve_rendered = {
callback(null, response); callback(null, response);
} }
} else if (source_type === 'mbtiles') { } else if (sourceType === 'mbtiles') {
source.getTile(z, x, y, (err, data, headers) => { source.getTile(z, x, y, (err, data, headers) => {
if (err) { if (err) {
if (options.verbose) if (options.verbose)
@ -1087,8 +1013,8 @@ export const serve_rendered = {
createCallback(null, renderer); createCallback(null, renderer);
}; };
return new advancedPool.Pool({ return new advancedPool.Pool({
min: min, min,
max: max, max,
create: createRenderer.bind(null, ratio), create: createRenderer.bind(null, ratio),
destroy: (renderer) => { destroy: (renderer) => {
renderer.release(); renderer.release();
@ -1163,7 +1089,7 @@ export const serve_rendered = {
const queue = []; const queue = [];
for (const name of Object.keys(styleJSON.sources)) { for (const name of Object.keys(styleJSON.sources)) {
let source_type; let sourceType;
let source = styleJSON.sources[name]; let source = styleJSON.sources[name];
let url = source.url; let url = source.url;
if ( if (
@ -1184,10 +1110,10 @@ export const serve_rendered = {
} }
let inputFile; let inputFile;
const DataInfo = dataResolver(dataId); const dataInfo = dataResolver(dataId);
if (DataInfo.inputfile) { if (dataInfo.inputFile) {
inputFile = DataInfo.inputfile; inputFile = dataInfo.inputFile;
source_type = DataInfo.filetype; sourceType = dataInfo.fileType;
} else { } else {
console.error(`ERROR: data "${inputFile}" not found!`); console.error(`ERROR: data "${inputFile}" not found!`);
process.exit(1); process.exit(1);
@ -1200,10 +1126,10 @@ export const serve_rendered = {
} }
} }
if (source_type === 'pmtiles') { if (sourceType === 'pmtiles') {
map.sources[name] = PMtilesOpen(inputFile); map.sources[name] = openPMtiles(inputFile);
map.source_types[name] = 'pmtiles'; map.sourceTypes[name] = 'pmtiles';
const metadata = await GetPMtilesInfo(map.sources[name]); const metadata = await getPMtilesInfo(map.sources[name]);
if (!repoobj.dataProjWGStoInternalWGS && metadata.proj4) { if (!repoobj.dataProjWGStoInternalWGS && metadata.proj4) {
// how to do this for multiple sources with different proj4 defs? // how to do this for multiple sources with different proj4 defs?
@ -1248,7 +1174,7 @@ export const serve_rendered = {
console.error(err); console.error(err);
return; return;
} }
map.source_types[name] = 'mbtiles'; map.sourceTypes[name] = 'mbtiles';
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) { if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
// how to do this for multiple sources with different proj4 defs? // how to do this for multiple sources with different proj4 defs?
@ -1296,7 +1222,8 @@ export const serve_rendered = {
} }
} }
const renderersReadyPromise = Promise.all(queue).then(() => { await Promise.all(queue);
// standard and @2x tiles are much more usual -> default to larger pools // standard and @2x tiles are much more usual -> default to larger pools
const minPoolSizes = options.minRendererPoolSizes || [8, 4, 2]; const minPoolSizes = options.minRendererPoolSizes || [8, 4, 2];
const maxPoolSizes = options.maxRendererPoolSizes || [16, 8, 4]; const maxPoolSizes = options.maxRendererPoolSizes || [16, 8, 4];
@ -1306,16 +1233,13 @@ export const serve_rendered = {
const minPoolSize = minPoolSizes[i]; const minPoolSize = minPoolSizes[i];
const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]); const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]);
map.renderers[s] = createPool(s, 'tile', minPoolSize, maxPoolSize); map.renderers[s] = createPool(s, 'tile', minPoolSize, maxPoolSize);
map.renderers_static[s] = createPool( map.renderersStatic[s] = createPool(
s, s,
'static', 'static',
minPoolSize, minPoolSize,
maxPoolSize, maxPoolSize,
); );
} }
});
return renderersReadyPromise;
}, },
remove: (repo, id) => { remove: (repo, id) => {
const item = repo[id]; const item = repo[id];
@ -1323,7 +1247,7 @@ export const serve_rendered = {
item.map.renderers.forEach((pool) => { item.map.renderers.forEach((pool) => {
pool.close(); pool.close();
}); });
item.map.renderers_static.forEach((pool) => { item.map.renderersStatic.forEach((pool) => {
pool.close(); pool.close();
}); });
} }

View file

@ -11,12 +11,12 @@ import { getPublicUrl } from './utils.js';
const httpTester = /^(http(s)?:)?\/\//; const httpTester = /^(http(s)?:)?\/\//;
const fixUrl = (req, url, publicUrl, opt_nokey) => { const fixUrl = (req, url, publicUrl) => {
if (!url || typeof url !== 'string' || url.indexOf('local://') !== 0) { if (!url || typeof url !== 'string' || url.indexOf('local://') !== 0) {
return url; return url;
} }
const queryParams = []; const queryParams = [];
if (!opt_nokey && req.query.key) { if (req.query.key) {
queryParams.unshift(`key=${encodeURIComponent(req.query.key)}`); queryParams.unshift(`key=${encodeURIComponent(req.query.key)}`);
} }
let query = ''; let query = '';
@ -42,20 +42,10 @@ export const serve_style = {
} }
// mapbox-gl-js viewer cannot handle sprite urls with query // mapbox-gl-js viewer cannot handle sprite urls with query
if (styleJSON_.sprite) { if (styleJSON_.sprite) {
styleJSON_.sprite = fixUrl( styleJSON_.sprite = fixUrl(req, styleJSON_.sprite, item.publicUrl);
req,
styleJSON_.sprite,
item.publicUrl,
false,
);
} }
if (styleJSON_.glyphs) { if (styleJSON_.glyphs) {
styleJSON_.glyphs = fixUrl( styleJSON_.glyphs = fixUrl(req, styleJSON_.glyphs, item.publicUrl);
req,
styleJSON_.glyphs,
item.publicUrl,
false,
);
} }
return res.send(styleJSON_); return res.send(styleJSON_);
}); });

View file

@ -141,11 +141,8 @@ function start(opts) {
// Load all available icons into a settings object // Load all available icons into a settings object
startupPromises.push( startupPromises.push(
new Promise((resolve) => {
getFiles(paths.icons).then((files) => { getFiles(paths.icons).then((files) => {
paths.availableIcons = files; paths.availableIcons = files;
resolve();
});
}), }),
); );
@ -182,15 +179,15 @@ function start(opts) {
item, item,
id, id,
opts.publicUrl, opts.publicUrl,
(StyleSourceId, protocol) => { (styleSourceId, protocol) => {
let dataItemId; let dataItemId;
for (const id of Object.keys(data)) { for (const id of Object.keys(data)) {
if (id === StyleSourceId) { if (id === styleSourceId) {
// Style id was found in data ids, return that id // Style id was found in data ids, return that id
dataItemId = id; dataItemId = id;
} else { } else {
const fileType = Object.keys(data[id])[0]; const fileType = Object.keys(data[id])[0];
if (data[id][fileType] === StyleSourceId) { if (data[id][fileType] === styleSourceId) {
// Style id was found in data filename, return the id that filename belong to // Style id was found in data filename, return the id that filename belong to
dataItemId = id; dataItemId = id;
} }
@ -202,21 +199,21 @@ function start(opts) {
} else { } else {
if (!allowMoreData) { if (!allowMoreData) {
console.log( console.log(
`ERROR: style "${item.style}" using unknown file "${StyleSourceId}"! Skipping...`, `ERROR: style "${item.style}" using unknown file "${styleSourceId}"! Skipping...`,
); );
return undefined; return undefined;
} else { } else {
let id = let id =
StyleSourceId.substr(0, StyleSourceId.lastIndexOf('.')) || styleSourceId.substr(0, styleSourceId.lastIndexOf('.')) ||
StyleSourceId; styleSourceId;
if (isValidHttpUrl(StyleSourceId)) { if (isValidHttpUrl(styleSourceId)) {
id = id =
fnv1a(StyleSourceId) + '_' + id.replace(/^.*\/(.*)$/, '$1'); fnv1a(styleSourceId) + '_' + id.replace(/^.*\/(.*)$/, '$1');
} }
while (data[id]) id += '_'; //if the data source id already exists, add a "_" untill it doesn't while (data[id]) id += '_'; //if the data source id already exists, add a "_" untill it doesn't
//Add the new data source to the data array. //Add the new data source to the data array.
data[id] = { data[id] = {
[protocol]: StyleSourceId, [protocol]: styleSourceId,
}; };
return id; return id;
@ -239,15 +236,15 @@ function start(opts) {
item, item,
id, id,
opts.publicUrl, opts.publicUrl,
(StyleSourceId) => { function dataResolver(styleSourceId) {
let fileType; let fileType;
let inputFile; let inputFile;
for (const id of Object.keys(data)) { for (const id of Object.keys(data)) {
fileType = Object.keys(data[id])[0]; fileType = Object.keys(data[id])[0];
if (StyleSourceId == id) { if (styleSourceId == id) {
inputFile = data[id][fileType]; inputFile = data[id][fileType];
break; break;
} else if (data[id][fileType] == StyleSourceId) { } else if (data[id][fileType] == styleSourceId) {
inputFile = data[id][fileType]; inputFile = data[id][fileType];
break; break;
} }
@ -256,7 +253,7 @@ function start(opts) {
inputFile = path.resolve(options.paths[fileType], inputFile); inputFile = path.resolve(options.paths[fileType], inputFile);
} }
return { inputfile: inputFile, filetype: fileType }; return { inputFile, fileType };
}, },
), ),
); );
@ -347,7 +344,7 @@ function start(opts) {
result.push({ result.push({
version: styleJSON.version, version: styleJSON.version,
name: styleJSON.name, name: styleJSON.name,
id: id, id,
url: `${getPublicUrl( url: `${getPublicUrl(
opts.publicUrl, opts.publicUrl,
req, req,
@ -633,9 +630,9 @@ function start(opts) {
enableShutdown(server); enableShutdown(server);
return { return {
app: app, app,
server: server, server,
startupPromise: startupPromise, startupPromise,
}; };
} }

View file

@ -1,7 +1,8 @@
'use strict'; 'use strict';
import path from 'path'; import path from 'path';
import fs from 'node:fs'; import fsPromises from 'fs/promises';
import fs, { existsSync } from 'node:fs';
import clone from 'clone'; import clone from 'clone';
import glyphCompose from '@mapbox/glyph-pbf-composite'; import glyphCompose from '@mapbox/glyph-pbf-composite';
@ -139,7 +140,7 @@ const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) =>
} }
}); });
export const getFontsPbf = ( export const getFontsPbf = async (
allowedFonts, allowedFonts,
fontPath, fontPath,
names, names,
@ -160,35 +161,24 @@ export const getFontsPbf = (
); );
} }
return Promise.all(queue).then((values) => glyphCompose.combine(values)); const values = await Promise.all(queue);
return glyphCompose.combine(values);
}; };
export const listFonts = async (fontPath) => { export const listFonts = async (fontPath) => {
const existingFonts = {}; const existingFonts = {};
const fontListingPromise = new Promise((resolve, reject) => {
fs.readdir(fontPath, (err, files) => { const files = await fsPromises.readdir(fontPath);
if (err) {
reject(err);
return;
}
for (const file of files) { for (const file of files) {
fs.stat(path.join(fontPath, file), (err, stats) => { const stats = await fsPromises.stat(path.join(fontPath, file));
if (err) {
reject(err);
return;
}
if ( if (
stats.isDirectory() && stats.isDirectory() &&
fs.existsSync(path.join(fontPath, file, '0-255.pbf')) existsSync(path.join(fontPath, file, '0-255.pbf'))
) { ) {
existingFonts[path.basename(file)] = true; existingFonts[path.basename(file)] = true;
} }
});
} }
resolve();
});
});
await fontListingPromise;
return existingFonts; return existingFonts;
}; };