feat: enable Google Polyline in Static Images endpoint 🕺 (#648)
* docs: update `encodedpath` documentation Co-Authored-By: Niklas Hösl <nik.hoesl@hotmail.com> Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * feat: enable `encodedpath` in query to load encoded polyline Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * refactor!: update static map endpoint `path` Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * chore: remove `console.log()` Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * docs: remove `encodedpath` arg Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * revert: docs update Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * chore(deps): add `@mapbox/polyline` dep Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * chore(deps): add `@mapbox/polyline` dep Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * fix: enable default `stroke` Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * docs: update documentation Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * test: decode URI component Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * fix: drawPath method Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * fix: safely decode URI Component Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * fix: enable multiple paths while extracting them from query Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> * fix: lockfile conflict Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com> Co-authored-by: Niklas Hösl <nik.hoesl@hotmail.com>
This commit is contained in:
parent
646f67e987
commit
bb0cd60e64
4 changed files with 1553 additions and 8103 deletions
|
@ -39,6 +39,8 @@ Static images
|
||||||
|
|
||||||
* e.g. ``5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8``
|
* e.g. ``5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8``
|
||||||
* can be provided multiple times
|
* can be provided multiple times
|
||||||
|
* or pass the path as per [Maptiler Cloud API](https://docs.maptiler.com/cloud/api/static-maps/)
|
||||||
|
* Match pattern: ((fill|stroke|width)\:[^\|]+\|)*((enc:.+)|((-?\d+\.?\d*,-?\d+\.?\d*\|)+(-?\d+\.?\d*,-?\d+\.?\d*)))
|
||||||
|
|
||||||
* ``latlng`` - indicates coordinates are in ``lat,lng`` order rather than the usual ``lng,lat``
|
* ``latlng`` - indicates coordinates are in ``lat,lng`` order rather than the usual ``lng,lat``
|
||||||
* ``fill`` - color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``)
|
* ``fill`` - color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``)
|
||||||
|
|
9396
package-lock.json
generated
9396
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -20,6 +20,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/glyph-pbf-composite": "0.0.3",
|
"@mapbox/glyph-pbf-composite": "0.0.3",
|
||||||
"@mapbox/mbtiles": "0.12.1",
|
"@mapbox/mbtiles": "0.12.1",
|
||||||
|
"@mapbox/polyline": "^1.1.1",
|
||||||
"@mapbox/sphericalmercator": "1.2.0",
|
"@mapbox/sphericalmercator": "1.2.0",
|
||||||
"@mapbox/vector-tile": "1.3.1",
|
"@mapbox/vector-tile": "1.3.1",
|
||||||
"@maplibre/maplibre-gl-native": "5.1.1",
|
"@maplibre/maplibre-gl-native": "5.1.1",
|
||||||
|
@ -38,9 +39,9 @@
|
||||||
"pbf": "3.2.1",
|
"pbf": "3.2.1",
|
||||||
"proj4": "2.8.0",
|
"proj4": "2.8.0",
|
||||||
"request": "2.88.2",
|
"request": "2.88.2",
|
||||||
|
"sanitize-filename": "1.6.3",
|
||||||
"sharp": "0.31.3",
|
"sharp": "0.31.3",
|
||||||
"tileserver-gl-styles": "2.0.0",
|
"tileserver-gl-styles": "2.0.0"
|
||||||
"sanitize-filename": "1.6.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.4.1",
|
"@commitlint/cli": "^17.4.1",
|
||||||
|
|
|
@ -15,11 +15,14 @@ import sanitize from 'sanitize-filename';
|
||||||
import SphericalMercator from '@mapbox/sphericalmercator';
|
import SphericalMercator from '@mapbox/sphericalmercator';
|
||||||
import mlgl from '@maplibre/maplibre-gl-native';
|
import mlgl from '@maplibre/maplibre-gl-native';
|
||||||
import MBTiles from '@mapbox/mbtiles';
|
import MBTiles from '@mapbox/mbtiles';
|
||||||
|
import polyline from '@mapbox/polyline';
|
||||||
import proj4 from 'proj4';
|
import proj4 from 'proj4';
|
||||||
import request from 'request';
|
import request from 'request';
|
||||||
import { getFontsPbf, getTileUrls, fixTileJSONCenter } from './utils.js';
|
import { getFontsPbf, getTileUrls, fixTileJSONCenter } from './utils.js';
|
||||||
|
|
||||||
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
|
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
|
||||||
|
const PATH_PATTERN =
|
||||||
|
/^((fill|stroke|width)\:[^\|]+\|)*((enc:.+)|((-?\d+\.?\d*,-?\d+\.?\d*\|)+(-?\d+\.?\d*,-?\d+\.?\d*)))/;
|
||||||
const httpTester = /^(http(s)?:)?\/\//;
|
const httpTester = /^(http(s)?:)?\/\//;
|
||||||
|
|
||||||
const mercator = new SphericalMercator();
|
const mercator = new SphericalMercator();
|
||||||
|
@ -147,29 +150,48 @@ const parseCoordinates = (coordinatePair, query, transformer) => {
|
||||||
* @param {Function} transformer Optional transform function.
|
* @param {Function} transformer Optional transform function.
|
||||||
*/
|
*/
|
||||||
const extractPathsFromQuery = (query, transformer) => {
|
const extractPathsFromQuery = (query, transformer) => {
|
||||||
// Return an empty list if no paths have been provided
|
// Initiate paths array
|
||||||
if (!query.path) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const paths = [];
|
const paths = [];
|
||||||
|
// Return an empty list if no paths have been provided
|
||||||
// Check if multiple paths have been provided and mimic a list if it's a
|
if ('path' in query && !query.path) {
|
||||||
// single path.
|
return paths;
|
||||||
|
}
|
||||||
|
// Parse paths provided via path query argument
|
||||||
|
if ('path' in query) {
|
||||||
const providedPaths = Array.isArray(query.path) ? query.path : [query.path];
|
const providedPaths = Array.isArray(query.path) ? query.path : [query.path];
|
||||||
|
|
||||||
// Iterate through paths, parse and validate them
|
// Iterate through paths, parse and validate them
|
||||||
for (const provided_path of providedPaths) {
|
for (const providedPath of providedPaths) {
|
||||||
|
// Logic for pushing coords to path when path includes google polyline
|
||||||
|
if (
|
||||||
|
providedPath.includes('enc:') &&
|
||||||
|
PATH_PATTERN.test(decodeURIComponent(providedPath))
|
||||||
|
) {
|
||||||
|
const encodedPaths = providedPath.split(',');
|
||||||
|
for (const path of encodedPaths) {
|
||||||
|
const line = path
|
||||||
|
.split('|')
|
||||||
|
.filter(
|
||||||
|
(x) =>
|
||||||
|
!x.startsWith('fill') &&
|
||||||
|
!x.startsWith('stroke') &&
|
||||||
|
!x.startsWith('width'),
|
||||||
|
)
|
||||||
|
.join('')
|
||||||
|
.replace('enc:', '');
|
||||||
|
const coords = polyline.decode(line).map(([lat, lng]) => [lng, lat]);
|
||||||
|
paths.push(coords);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Iterate through paths, parse and validate them
|
||||||
const currentPath = [];
|
const currentPath = [];
|
||||||
|
|
||||||
// Extract coordinate-list from path
|
// Extract coordinate-list from path
|
||||||
const pathParts = (provided_path || '').split('|');
|
const pathParts = (providedPath || '').split('|');
|
||||||
|
|
||||||
// Iterate through coordinate-list, parse the coordinates and validate them
|
// Iterate through coordinate-list, parse the coordinates and validate them
|
||||||
for (const pair of pathParts) {
|
for (const pair of pathParts) {
|
||||||
// Extract coordinates from coordinate pair
|
// Extract coordinates from coordinate pair
|
||||||
const pairParts = pair.split(',');
|
const pairParts = pair.split(',');
|
||||||
|
|
||||||
// Ensure we have two coordinates
|
// Ensure we have two coordinates
|
||||||
if (pairParts.length === 2) {
|
if (pairParts.length === 2) {
|
||||||
const pair = parseCoordinates(pairParts, query, transformer);
|
const pair = parseCoordinates(pairParts, query, transformer);
|
||||||
|
@ -183,12 +205,13 @@ const extractPathsFromQuery = (query, transformer) => {
|
||||||
currentPath.push(pair);
|
currentPath.push(pair);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extend list of paths with current path if it contains coordinates
|
// Extend list of paths with current path if it contains coordinates
|
||||||
if (currentPath.length) {
|
if (currentPath.length) {
|
||||||
paths.push(currentPath);
|
paths.push(currentPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return paths;
|
return paths;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -422,6 +445,7 @@ const drawMarkers = async (ctx, markers, z) => {
|
||||||
* @param {number} z Map zoom level.
|
* @param {number} z Map zoom level.
|
||||||
*/
|
*/
|
||||||
const drawPath = (ctx, path, query, z) => {
|
const drawPath = (ctx, path, query, z) => {
|
||||||
|
const renderPath = (splitPaths) => {
|
||||||
if (!path || path.length < 2) {
|
if (!path || path.length < 2) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -443,16 +467,35 @@ const drawPath = (ctx, path, query, z) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optionally fill drawn shape with a rgba color from query
|
// Optionally fill drawn shape with a rgba color from query
|
||||||
if (query.fill !== undefined) {
|
const pathHasFill =
|
||||||
ctx.fillStyle = query.fill;
|
splitPaths.filter((x) => x.startsWith('fill')).length > 0;
|
||||||
|
if (query.fill !== undefined || pathHasFill) {
|
||||||
|
if ('fill' in query) {
|
||||||
|
ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)';
|
||||||
|
}
|
||||||
|
if (pathHasFill) {
|
||||||
|
ctx.fillStyle = splitPaths
|
||||||
|
.find((x) => x.startsWith('fill:'))
|
||||||
|
.replace('fill:', '');
|
||||||
|
}
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get line width from query and fall back to 1 if not provided
|
// Get line width from query and fall back to 1 if not provided
|
||||||
const lineWidth = query.width !== undefined ? parseFloat(query.width) : 1;
|
const pathHasWidth =
|
||||||
|
splitPaths.filter((x) => x.startsWith('width')).length > 0;
|
||||||
// Ensure line width is valid
|
if (query.width !== undefined || pathHasWidth) {
|
||||||
if (lineWidth > 0) {
|
let lineWidth = 1;
|
||||||
|
// Get line width from query
|
||||||
|
if ('width' in query) {
|
||||||
|
lineWidth = Number(query.width);
|
||||||
|
}
|
||||||
|
// Get line width from path in query
|
||||||
|
if (pathHasWidth) {
|
||||||
|
lineWidth = Number(
|
||||||
|
splitPaths.find((x) => x.startsWith('width:')).replace('width:', ''),
|
||||||
|
);
|
||||||
|
}
|
||||||
// Get border width from query and fall back to 10% of line width
|
// Get border width from query and fall back to 10% of line width
|
||||||
const borderWidth =
|
const borderWidth =
|
||||||
query.borderwidth !== undefined
|
query.borderwidth !== undefined
|
||||||
|
@ -477,10 +520,34 @@ const drawPath = (ctx, path, query, z) => {
|
||||||
ctx.strokeStyle = query.border;
|
ctx.strokeStyle = query.border;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.lineWidth = lineWidth;
|
ctx.lineWidth = lineWidth;
|
||||||
ctx.strokeStyle = query.stroke || 'rgba(0,64,255,0.7)';
|
}
|
||||||
|
|
||||||
|
const pathHasStroke =
|
||||||
|
splitPaths.filter((x) => x.startsWith('stroke')).length > 0;
|
||||||
|
if (query.stroke !== undefined || pathHasStroke) {
|
||||||
|
if ('stroke' in query) {
|
||||||
|
ctx.strokeStyle = query.stroke;
|
||||||
|
}
|
||||||
|
// Path Width gets higher priority
|
||||||
|
if (pathHasWidth) {
|
||||||
|
ctx.strokeStyle = splitPaths
|
||||||
|
.find((x) => x.startsWith('stroke:'))
|
||||||
|
.replace('stroke:', '');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.strokeStyle = 'rgba(0,64,255,0.7)';
|
||||||
|
}
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if path in query is valid
|
||||||
|
if (Array.isArray(query.path)) {
|
||||||
|
for (let i = 0; i < query.path.length; i += 1) {
|
||||||
|
renderPath(decodeURIComponent(query.path.at(i)).split('|'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderPath(decodeURIComponent(query.path).split('|'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue