* test: using 512px rendered tiles Based on https://github.com/maptiler/tileserver-gl/pull/495 Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: use static renderer at zoom 0 renderer is not able to change the size of tile to more than 512px in tile mode Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * Add support for 512 sized raster tiles (#1) * Enable setting tilesize for raster tiles * Serve correct endpoint for raster tiles * Add 256 & 512 sized raster layers to wmts getCapabilities document * Update wmts getCapabilities tileMatrixSets * Add rendered tiles format for getTileUrls method * Update endpoints documentation * Add and fix tiles_rendered tests * fix: formatting Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: corrent bad merge Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: if tile size is undefined don't add it needed for data endpoint right now Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: set tile size in raster endpoints to 512 Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: add semicolon Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: lint Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: test z1 512px file that actually exists Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: make tileSize optional Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: cleaner if statement Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * docs: update tileSize to show as optional Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: allow tile size in data url Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: remove unneeded tileSize Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: set data as 256 tileSize for consistency Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * docs: add note about optional data tileSize Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: lint Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * Revert "fix: remove unneeded tileSize" This reverts commit a4583161bf53653d65a4606c407ba9b5249d1061. * fix: allow tile size to be set at json endpoints Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: set default endpoint tilesizes Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: don't use tilesize one data endpoint It doesn't do anything Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: default style endpoint to undefined Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: zoom 0 workaround Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * Revert "fix: zoom 0 workaround" This reverts commit 0f3bbd765c9c151016cec66768675f990676c8b3. * fix: limit min zoom to 1 in viewer Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: put back orig string Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * docs: add optional tilesize to TileJSON Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: cleanup thumbnails Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: default undefined like other data endpoints Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: move data xyz_link Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: remove console.log Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: lint Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * chore: update version Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: update path Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: join error Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> * fix: revert path change Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> --------- Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net> Co-authored-by: Petteri Pesonen <teemu.p.pesonen@gmail.com>
208 lines
5.3 KiB
JavaScript
208 lines
5.3 KiB
JavaScript
'use strict';
|
|
|
|
import path from 'path';
|
|
import fsPromises from 'fs/promises';
|
|
import fs, { existsSync } from 'node:fs';
|
|
import clone from 'clone';
|
|
import glyphCompose from '@mapbox/glyph-pbf-composite';
|
|
|
|
/**
|
|
* Generate new URL object
|
|
* @param req
|
|
* @params {object} req - Express request
|
|
* @returns {URL} object
|
|
*/
|
|
const getUrlObject = (req) => {
|
|
const urlObject = new URL(`${req.protocol}://${req.headers.host}/`);
|
|
// support overriding hostname by sending X-Forwarded-Host http header
|
|
urlObject.hostname = req.hostname;
|
|
return urlObject;
|
|
};
|
|
|
|
export const getPublicUrl = (publicUrl, req) => {
|
|
if (publicUrl) {
|
|
return publicUrl;
|
|
}
|
|
return getUrlObject(req).toString();
|
|
};
|
|
|
|
export const getTileUrls = (
|
|
req,
|
|
domains,
|
|
path,
|
|
tileSize,
|
|
format,
|
|
publicUrl,
|
|
aliases,
|
|
) => {
|
|
const urlObject = getUrlObject(req);
|
|
if (domains) {
|
|
if (domains.constructor === String && domains.length > 0) {
|
|
domains = domains.split(',');
|
|
}
|
|
const hostParts = urlObject.host.split('.');
|
|
const relativeSubdomainsUsable =
|
|
hostParts.length > 1 &&
|
|
!/^([0-9]{1,3}\.){3}[0-9]{1,3}(\:[0-9]+)?$/.test(urlObject.host);
|
|
const newDomains = [];
|
|
for (const domain of domains) {
|
|
if (domain.indexOf('*') !== -1) {
|
|
if (relativeSubdomainsUsable) {
|
|
const newParts = hostParts.slice(1);
|
|
newParts.unshift(domain.replace('*', hostParts[0]));
|
|
newDomains.push(newParts.join('.'));
|
|
}
|
|
} else {
|
|
newDomains.push(domain);
|
|
}
|
|
}
|
|
domains = newDomains;
|
|
}
|
|
if (!domains || domains.length == 0) {
|
|
domains = [urlObject.host];
|
|
}
|
|
|
|
const queryParams = [];
|
|
if (req.query.key) {
|
|
queryParams.push(`key=${encodeURIComponent(req.query.key)}`);
|
|
}
|
|
if (req.query.style) {
|
|
queryParams.push(`style=${encodeURIComponent(req.query.style)}`);
|
|
}
|
|
const query = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
|
|
|
if (aliases && aliases[format]) {
|
|
format = aliases[format];
|
|
}
|
|
|
|
let tileParams = `{z}/{x}/{y}`;
|
|
if (tileSize && ['png', 'jpg', 'jpeg', 'webp'].includes(format)) {
|
|
tileParams = `${tileSize}/{z}/{x}/{y}`;
|
|
}
|
|
|
|
const uris = [];
|
|
if (!publicUrl) {
|
|
for (const domain of domains) {
|
|
uris.push(
|
|
`${req.protocol}://${domain}/${path}/${tileParams}.${format}${query}`,
|
|
);
|
|
}
|
|
} else {
|
|
uris.push(`${publicUrl}${path}/${tileParams}.${format}${query}`);
|
|
}
|
|
|
|
return uris;
|
|
};
|
|
|
|
export const fixTileJSONCenter = (tileJSON) => {
|
|
if (tileJSON.bounds && !tileJSON.center) {
|
|
const fitWidth = 1024;
|
|
const tiles = fitWidth / 256;
|
|
tileJSON.center = [
|
|
(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,
|
|
),
|
|
];
|
|
}
|
|
};
|
|
|
|
const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) =>
|
|
new Promise((resolve, reject) => {
|
|
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
|
|
const filename = path.join(fontPath, name, `${range}.pbf`);
|
|
if (!fallbacks) {
|
|
fallbacks = clone(allowedFonts || {});
|
|
}
|
|
delete fallbacks[name];
|
|
fs.readFile(filename, (err, data) => {
|
|
if (err) {
|
|
console.error(`ERROR: Font not found: ${name}`);
|
|
if (fallbacks && Object.keys(fallbacks).length) {
|
|
let fallbackName;
|
|
|
|
let fontStyle = name.split(' ').pop();
|
|
if (['Regular', 'Bold', 'Italic'].indexOf(fontStyle) < 0) {
|
|
fontStyle = 'Regular';
|
|
}
|
|
fallbackName = `Noto Sans ${fontStyle}`;
|
|
if (!fallbacks[fallbackName]) {
|
|
fallbackName = `Open Sans ${fontStyle}`;
|
|
if (!fallbacks[fallbackName]) {
|
|
fallbackName = Object.keys(fallbacks)[0];
|
|
}
|
|
}
|
|
|
|
console.error(`ERROR: Trying to use ${fallbackName} as a fallback`);
|
|
delete fallbacks[fallbackName];
|
|
getFontPbf(null, fontPath, fallbackName, range, fallbacks).then(
|
|
resolve,
|
|
reject,
|
|
);
|
|
} else {
|
|
reject(`Font load error: ${name}`);
|
|
}
|
|
} else {
|
|
resolve(data);
|
|
}
|
|
});
|
|
} else {
|
|
reject(`Font not allowed: ${name}`);
|
|
}
|
|
});
|
|
|
|
export const getFontsPbf = async (
|
|
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),
|
|
),
|
|
);
|
|
}
|
|
|
|
const values = await Promise.all(queue);
|
|
return glyphCompose.combine(values);
|
|
};
|
|
|
|
export const listFonts = async (fontPath) => {
|
|
const existingFonts = {};
|
|
|
|
const files = await fsPromises.readdir(fontPath);
|
|
for (const file of files) {
|
|
const stats = await fsPromises.stat(path.join(fontPath, file));
|
|
if (
|
|
stats.isDirectory() &&
|
|
existsSync(path.join(fontPath, file, '0-255.pbf'))
|
|
) {
|
|
existingFonts[path.basename(file)] = true;
|
|
}
|
|
}
|
|
|
|
return existingFonts;
|
|
};
|
|
|
|
export const isValidHttpUrl = (string) => {
|
|
let url;
|
|
|
|
try {
|
|
url = new URL(string);
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
|
|
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
};
|