add addzone and update zone in map-adapter interface
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
import {readFileSync} from 'fs';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {dirname, join} from 'path';
|
||||
|
||||
const packageJSONPath = join(dirname(fileURLToPath(import.meta.url)), '../package.json');
|
||||
const packageJSON = JSON.parse(readFileSync(packageJSONPath, 'utf8'));
|
||||
|
||||
export default
|
||||
`/**
|
||||
* MapLibre GL JS
|
||||
* @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v${packageJSON.version}/LICENSE.txt
|
||||
*/`;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* This script updates the changelog.md file with the version given in the arguments
|
||||
* It replaces ## main with ## <version>
|
||||
* Removes _...Add new stuff here..._
|
||||
* And adds on top a ## main with add stuff here.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
|
||||
const changelogPath = 'CHANGELOG.md';
|
||||
let changelog = fs.readFileSync(changelogPath, 'utf8');
|
||||
changelog = changelog.replace('## main', `## ${process.argv[2]}`);
|
||||
changelog = changelog.replaceAll('- _...Add new stuff here..._\n', '');
|
||||
changelog = `## main
|
||||
|
||||
### ✨ Features and improvements
|
||||
- _...Add new stuff here..._
|
||||
|
||||
### 🐞 Bug fixes
|
||||
- _...Add new stuff here..._
|
||||
|
||||
` + changelog;
|
||||
|
||||
fs.writeFileSync(changelogPath, changelog, 'utf8');
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "fs";
|
||||
import zlib from "zlib";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
const beforeSourcemap = JSON.parse(fs.readFileSync('./before.json').toString());
|
||||
const afterSourcemap = JSON.parse(fs.readFileSync('./after.json').toString());
|
||||
|
||||
function fileSize(file) {
|
||||
const {size} = fs.statSync(file);
|
||||
const gzipped = zlib.gzipSync(fs.readFileSync(file)).length
|
||||
return {
|
||||
size,
|
||||
gzipped
|
||||
};
|
||||
}
|
||||
|
||||
const beforejs = fileSize('./before/maplibre-gl.js');
|
||||
const beforecss = fileSize('./before/maplibre-gl.css');
|
||||
const afterjs = fileSize('./after/maplibre-gl.js');
|
||||
const aftercss = fileSize('./after/maplibre-gl.css');
|
||||
|
||||
console.log('Bundle size report:\n');
|
||||
console.log(`**Size Change:** ${prettyBytes(afterjs.gzipped + aftercss.gzipped - (beforejs.gzipped + beforecss.gzipped), { signed: true })}`);
|
||||
console.log(`**Total Size Before:** ${prettyBytes(beforejs.gzipped + beforecss.gzipped)}`);
|
||||
console.log(`**Total Size After:** ${prettyBytes(afterjs.gzipped + aftercss.gzipped)}`);
|
||||
console.log(`
|
||||
| Output file | Before | After | Change |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| maplibre-gl.js | ${prettyBytes(beforejs.gzipped)} | ${prettyBytes(afterjs.gzipped)} | ${prettyBytes(afterjs.gzipped - beforejs.gzipped, { signed: true })} |
|
||||
| maplibre-gl.css | ${prettyBytes(beforecss.gzipped)} | ${prettyBytes(aftercss.gzipped)} | ${prettyBytes(aftercss.gzipped - beforecss.gzipped, { signed: true })} |`);
|
||||
|
||||
const before = {};
|
||||
beforeSourcemap.results.forEach(result => {
|
||||
Object.keys(result.files).forEach(filename => {
|
||||
const {size} = result.files[filename];
|
||||
before[filename] = size;
|
||||
});
|
||||
});
|
||||
|
||||
const after = {};
|
||||
afterSourcemap.results.forEach(result => {
|
||||
Object.keys(result.files).forEach(filename => {
|
||||
const {size} = result.files[filename];
|
||||
after[filename] = size;
|
||||
});
|
||||
});
|
||||
|
||||
const diffs = [];
|
||||
Object.keys(Object.assign({}, before, after)).forEach(filename => {
|
||||
const beforeSize = before[filename] || 0;
|
||||
const afterSize = after[filename] || 0;
|
||||
if (Math.abs(afterSize - beforeSize) > 0) {
|
||||
diffs.push([
|
||||
afterSize - beforeSize, // for sorting
|
||||
filename.replace(/^[\./]+/, ''), // omit ../
|
||||
prettyBytes(beforeSize),
|
||||
prettyBytes(afterSize),
|
||||
prettyBytes(afterSize - beforeSize, { signed: true })
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
diffs.sort((a, b) => b[0] - a[0]);
|
||||
|
||||
console.log(`
|
||||
<details><summary>ℹ️ <strong>View Details</strong></summary>`);
|
||||
if (diffs.length) {
|
||||
console.log(`
|
||||
| Source file | Before | After | Change |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
${diffs.map(diff => '| ' + diff.slice(1).join(' | ') + ' |').join('\n')}
|
||||
`);
|
||||
} else {
|
||||
console.log('No major changes');
|
||||
}
|
||||
console.log(`</details>`);
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
/// js files in this project are esm.
|
||||
/// This package.json ensures that node imports from outside see them as such
|
||||
// https://nodejs.org/api/packages.html#type
|
||||
|
||||
import { writeFile, mkdir, copyFile } from "node:fs/promises"
|
||||
|
||||
async function ensureDist() {
|
||||
const dist = new URL("../dist/", import.meta.url);
|
||||
await mkdir(dist, { recursive: true })
|
||||
return dist
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
new URL("package.json", await ensureDist()),
|
||||
JSON.stringify({
|
||||
name: "maplibre-gl",
|
||||
type: "commonjs",
|
||||
deprecated: "Please install maplibre-gl from parent directory instead",
|
||||
})
|
||||
)
|
||||
|
||||
await copyFile(
|
||||
"./LICENSE.txt",
|
||||
new URL("LICENSE.txt", await ensureDist())
|
||||
)
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import puppeteer from 'puppeteer';
|
||||
import packageJson from '../package.json' with { type: 'json' };
|
||||
|
||||
const exampleName = process.argv[2];
|
||||
const useLocalhost = (process.argv.length > 3) && (process.argv[3] === 'serve');
|
||||
const examplePath = path.resolve('test', 'examples');
|
||||
|
||||
const browser = await puppeteer.launch({headless: exampleName === 'all'});
|
||||
|
||||
const page = await browser.newPage();
|
||||
// set viewport and double deviceScaleFactor to get a closer shot of the map
|
||||
await page.setViewport({
|
||||
width: 600,
|
||||
height: 250,
|
||||
deviceScaleFactor: 2
|
||||
});
|
||||
|
||||
async function createImage(exampleName) {
|
||||
// get the example contents
|
||||
if (useLocalhost) {
|
||||
console.log('Using localhost to serve examples.');
|
||||
await page.goto(`http://localhost:9966/test/examples/${exampleName}.html`);
|
||||
} else {
|
||||
const html = fs.readFileSync(path.resolve(examplePath, `${exampleName}.html`), 'utf-8');
|
||||
await page.setContent(html.replaceAll('../../dist', `https://unpkg.com/maplibre-gl@${packageJson.version}/dist`));
|
||||
}
|
||||
|
||||
// Wait for map to load, then wait two more seconds for images, etc. to load.
|
||||
try {
|
||||
// @ts-ignore
|
||||
await page.evaluate(() => document.querySelector('.maplibregl-ctrl-attrib').style.display = 'none');
|
||||
await page.waitForFunction('map.loaded()', {timeout: 10000});
|
||||
// Wait for 5 seconds on 3d model examples, since this takes longer to load.
|
||||
const waitTime = (exampleName.includes('3d-model') || exampleName.includes('globe')) ? 5000 : 1500;
|
||||
console.log(`waiting for ${waitTime} ms`);
|
||||
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||||
} catch {
|
||||
// map.loaded() does not evaluate to true within 3 seconds, it's probably an animated example.
|
||||
// In this case we take the screenshot immediately.
|
||||
console.log(`Timed out waiting for map load on ${exampleName}.`);
|
||||
}
|
||||
|
||||
await page
|
||||
.screenshot({
|
||||
path: `./docs/assets/examples/${exampleName}.png`,
|
||||
type: 'png',
|
||||
clip: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 600,
|
||||
height: 250
|
||||
}
|
||||
})
|
||||
.then(() => console.log(`Created ./docs/assets/examples/${exampleName}.png`))
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (exampleName === 'all') {
|
||||
const allFiles = fs.readdirSync(examplePath).filter(f => f.endsWith('html'));
|
||||
console.log(`Generating ${allFiles.length} images.`);
|
||||
for (const file of allFiles) {
|
||||
await createImage(file.replace('.html', ''));
|
||||
}
|
||||
} else if (exampleName) {
|
||||
await createImage(exampleName);
|
||||
} else {
|
||||
throw new Error(`
|
||||
Usage: npm run generate-images <file-name|all> [serve]
|
||||
file-name: the name of the example file in test/examples without the .html extension.
|
||||
all: generate images for all examples.
|
||||
serve: use localhost to serve examples - use 'npm run start' with this option, otherwise it will use the latest published version in npm.
|
||||
Example: npm run generate-images 3d-buildings serve`
|
||||
);
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import typedocConfig from '../typedoc.json' with {type: 'json'};
|
||||
import packageJson from '../package.json' with {type: 'json'};
|
||||
import {get} from 'https';
|
||||
import sharp from 'sharp';
|
||||
|
||||
type HtmlDoc = {
|
||||
title: string;
|
||||
description: string;
|
||||
mdFileName: string;
|
||||
};
|
||||
|
||||
function generateAPIIntroMarkdown(lines: string[]): string {
|
||||
let intro = `# Intro
|
||||
|
||||
This file is intended as a reference for the important and public classes of this API.
|
||||
We recommend looking at the [examples](../examples/index.md) as they will help you the most to start with MapLibre.
|
||||
|
||||
Most of the classes written here have an "Options" object for initialization, it is recommended to check which options exist.
|
||||
|
||||
It is recommended to import what you need and then use it. Some examples for classes assume you did that.
|
||||
For example, import the \`Map\` class like this:
|
||||
\`\`\`ts
|
||||
import {Map} from 'maplibre-gl';
|
||||
const map = new Map(...)
|
||||
\`\`\`
|
||||
|
||||
Import declarations are omitted from the examples for brevity.
|
||||
|
||||
`;
|
||||
intro += lines.map(l => l.replace('../', './')).join('\n');
|
||||
return intro;
|
||||
}
|
||||
|
||||
function generateMarkdownForExample(title: string, description: string, file: string, htmlContent: string): string {
|
||||
return `
|
||||
# ${title}
|
||||
|
||||
${description}
|
||||
|
||||
<iframe src="../${file}" width="100%" style="border:none; height:400px"></iframe>
|
||||
|
||||
\`\`\`html
|
||||
${htmlContent}
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
|
||||
async function generateMarkdownIndexFileOfAllExamplesAndPackImages(indexArray: HtmlDoc[]): Promise<string> {
|
||||
let indexMarkdown = '# Overview \n\n';
|
||||
const promises: Promise<any>[] = [];
|
||||
for (const indexArrayItem of indexArray) {
|
||||
const imagePath = `docs/assets/examples/${indexArrayItem.mdFileName!.replace('.md', '.png')}`;
|
||||
const outputPath = imagePath.replace('.png', '.webp');
|
||||
promises.push(sharp(imagePath).webp({quality: 90, lossless: false}).toFile(outputPath));
|
||||
indexMarkdown += `
|
||||
## [${indexArrayItem.title}](./${indexArrayItem.mdFileName})
|
||||
|
||||
}){ loading=lazy }
|
||||
|
||||
${indexArrayItem.description}
|
||||
`;
|
||||
}
|
||||
await Promise.all(promises);
|
||||
return indexMarkdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the README.md file by parsing the modules.md file generated by typedoc.
|
||||
*/
|
||||
function generateReadme() {
|
||||
const globalsFile = path.join(typedocConfig.out, 'globals.md');
|
||||
const content = fs.readFileSync(globalsFile, 'utf-8');
|
||||
let lines = content.split('\n');
|
||||
const classesLineIndex = lines.indexOf(lines.find(l => l.endsWith('Classes')) as string);
|
||||
lines = lines.splice(2, classesLineIndex - 2);
|
||||
const contentString = generateAPIIntroMarkdown(lines);
|
||||
fs.writeFileSync(path.join(typedocConfig.out, 'README.md'), contentString);
|
||||
fs.rmSync(globalsFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* This takes the examples folder with all the html files and generates a markdown file for each of them.
|
||||
* It also create an index file with all the examples and their images.
|
||||
*/
|
||||
async function generateExamplesFolder() {
|
||||
const examplesDocsFolder = path.join('docs', 'examples');
|
||||
if (fs.existsSync(examplesDocsFolder)) {
|
||||
fs.rmSync(examplesDocsFolder, {recursive: true, force: true});
|
||||
}
|
||||
fs.mkdirSync(examplesDocsFolder);
|
||||
const examplesFolder = path.join('test', 'examples');
|
||||
const files = fs.readdirSync(examplesFolder).filter(f => f.endsWith('html'));
|
||||
const maplibreUnpkg = `https://unpkg.com/maplibre-gl@${packageJson.version}/`;
|
||||
const indexArray = [] as HtmlDoc[];
|
||||
for (const file of files) {
|
||||
const htmlFile = path.join(examplesFolder, file);
|
||||
let htmlContent = fs.readFileSync(htmlFile, 'utf-8');
|
||||
htmlContent = htmlContent.replace(/\.\.\/\.\.\//g, maplibreUnpkg);
|
||||
htmlContent = htmlContent.replace(/-dev.js/g, '.js');
|
||||
const htmlContentLines = htmlContent.split('\n');
|
||||
const title = htmlContentLines.find(l => l.includes('<title'))?.replace('<title>', '').replace('</title>', '').trim()!;
|
||||
const description = htmlContentLines.find(l => l.includes('og:description'))?.replace(/.*content=\"(.*)\".*/, '$1')!;
|
||||
fs.writeFileSync(path.join(examplesDocsFolder, file), htmlContent);
|
||||
const mdFileName = file.replace('.html', '.md');
|
||||
indexArray.push({
|
||||
title,
|
||||
description,
|
||||
mdFileName
|
||||
});
|
||||
const exampleMarkdown = generateMarkdownForExample(title, description, file, htmlContent);
|
||||
fs.writeFileSync(path.join(examplesDocsFolder, mdFileName), exampleMarkdown);
|
||||
}
|
||||
|
||||
const indexMarkdown = await generateMarkdownIndexFileOfAllExamplesAndPackImages(indexArray);
|
||||
fs.writeFileSync(path.join(examplesDocsFolder, 'index.md'), indexMarkdown);
|
||||
}
|
||||
|
||||
async function fetchUrlContent(url: string) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
get(url, (res) => {
|
||||
let data = '';
|
||||
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
||||
reject(new Error(res.statusMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function generatePluginsPage() {
|
||||
/**
|
||||
* It extract some sections from Awesome MapLibre README.md so we can integrate it into our plugins page
|
||||
*
|
||||
* ```
|
||||
* header
|
||||
* <!-- [SOME-ID]:BEGIN -->
|
||||
* CONTENT-TO-EXTRACT
|
||||
* <!-- [SOME-ID]:END -->
|
||||
* footer
|
||||
* ```
|
||||
*/
|
||||
const awesomeReadmeUrl = 'https://raw.githubusercontent.com/maplibre/awesome-maplibre/main/README.md';
|
||||
const awesomeReadme = await fetchUrlContent(awesomeReadmeUrl);
|
||||
|
||||
const contentGroupsRE = /<!--\s*\[([-a-zA-Z]+)\]:BEGIN\s*-->([\s\S]*?)<!--\s*\[\1\]:END\s*-->/g;
|
||||
|
||||
const matches = awesomeReadme.matchAll(contentGroupsRE);
|
||||
const groups = Object.fromEntries(
|
||||
Array.from(matches).map(([, key, content]) => [key, content])
|
||||
);
|
||||
|
||||
const pluginsContent = `# Plugins
|
||||
|
||||
${groups['JAVASCRIPT-PLUGINS']}
|
||||
|
||||
## Framework Integrations
|
||||
|
||||
${groups['JAVASCRIPT-BINDINGS']}
|
||||
`;
|
||||
|
||||
fs.writeFileSync('docs/plugins.md', pluginsContent, {encoding: 'utf-8'});
|
||||
}
|
||||
|
||||
function updateMapLibreVersionForUNPKG() {
|
||||
|
||||
// Read index.md
|
||||
const indexPath = 'docs/index.md';
|
||||
let indexContent = fs.readFileSync(indexPath, 'utf-8');
|
||||
|
||||
// Replace the version number
|
||||
indexContent = indexContent.replace(/unpkg\.com\/maplibre-gl@\^(\d+\.\d+\.\d+)/g, `unpkg.com/maplibre-gl@^${packageJson.version}`);
|
||||
|
||||
// Save index.md
|
||||
fs.writeFileSync(indexPath, indexContent);
|
||||
}
|
||||
|
||||
// !!Main flow start here!!
|
||||
if (!fs.existsSync(typedocConfig.out)) {
|
||||
throw new Error('Please run typedoc generation first!');
|
||||
}
|
||||
fs.rmSync(path.join(typedocConfig.out, 'README.md'));
|
||||
generateReadme();
|
||||
await generateExamplesFolder();
|
||||
await generatePluginsPage();
|
||||
updateMapLibreVersionForUNPKG();
|
||||
console.log('Docs generation completed, to see it in action run\n npm run start-docs');
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import fs from 'fs';
|
||||
import {globSync} from 'glob';
|
||||
import path from 'path';
|
||||
|
||||
console.log('Generating shaders');
|
||||
|
||||
/**
|
||||
* This script is intended to copy the glsl file to the compilation output folder,
|
||||
* change their extension to .js and export the shaders as strings in javascript.
|
||||
* It will also minify them a bit if needed and change the extension to .js
|
||||
* After that it will create a combined typescript definition file and manipulate it a bit
|
||||
* It will also create a simple package.json file to allow importing this package in webpack
|
||||
*/
|
||||
|
||||
function glslToTs(code: string): string {
|
||||
code = code
|
||||
.trim() // strip whitespace at the start/end
|
||||
.replace(/\s*\/\/[^\n]*\n/g, '\n') // strip double-slash comments
|
||||
.replace(/\n+/g, '\n') // collapse multi line breaks
|
||||
.replace(/\n\s+/g, '\n') // strip indentation
|
||||
.replace(/\s?([+-\/*=,])\s?/g, '$1') // strip whitespace around operators
|
||||
.replace(/([;\(\),\{\}])\n(?=[^#])/g, '$1'); // strip more line breaks
|
||||
|
||||
return `// This file is generated. Edit build/generate-shaders.ts, then run \`npm run codegen\`.
|
||||
export default ${JSON.stringify(code).replaceAll('"', '\'')};\n`;
|
||||
}
|
||||
|
||||
const shaderFiles = globSync('./src/shaders/*.glsl');
|
||||
for (const file of shaderFiles) {
|
||||
const glslFile = fs.readFileSync(file, 'utf8');
|
||||
const tsSource = glslToTs(glslFile);
|
||||
const fileName = path.join('.', 'src', 'shaders', `${file.split(path.sep).splice(-1)}.g.ts`);
|
||||
fs.writeFileSync(fileName, tsSource);
|
||||
}
|
||||
|
||||
console.log(`Finished converting ${shaderFiles.length} shaders`);
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* Generates the following:
|
||||
* - data/array_types.js, which consists of:
|
||||
* - StructArrayLayout_* subclasses, one for each underlying memory layout we need
|
||||
* - Named exports mapping each conceptual array type (e.g., CircleLayoutArray) to its corresponding StructArrayLayout class
|
||||
* - Particular, named StructArray subclasses, when fancy struct accessors are needed (e.g. CollisionBoxArray)
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as util from '../src/util/util';
|
||||
import {createLayout, viewTypes} from '../src/util/struct_array';
|
||||
import type {ViewType, StructArrayLayout} from '../src/util/struct_array';
|
||||
|
||||
import posAttributes from '../src/data/pos_attributes';
|
||||
import pos3dAttributes from '../src/data/pos3d_attributes';
|
||||
import rasterBoundsAttributes from '../src/data/raster_bounds_attributes';
|
||||
import circleAttributes from '../src/data/bucket/circle_attributes';
|
||||
import fillAttributes from '../src/data/bucket/fill_attributes';
|
||||
import fillExtrusionAttributes from '../src/data/bucket/fill_extrusion_attributes';
|
||||
import {lineLayoutAttributes} from '../src/data/bucket/line_attributes';
|
||||
import {lineLayoutAttributesExt} from '../src/data/bucket/line_attributes_ext';
|
||||
import {patternAttributes} from '../src/data/bucket/pattern_attributes';
|
||||
// symbol layer specific arrays
|
||||
import {
|
||||
symbolLayoutAttributes,
|
||||
dynamicLayoutAttributes,
|
||||
placementOpacityAttributes,
|
||||
collisionBox,
|
||||
collisionBoxLayout,
|
||||
collisionCircleLayout,
|
||||
collisionVertexAttributes,
|
||||
quadTriangle,
|
||||
placement,
|
||||
symbolInstance,
|
||||
glyphOffset,
|
||||
lineVertex,
|
||||
textAnchorOffset
|
||||
} from '../src/data/bucket/symbol_attributes';
|
||||
|
||||
const typeAbbreviations = {
|
||||
'Int8': 'b',
|
||||
'Uint8': 'ub',
|
||||
'Int16': 'i',
|
||||
'Uint16': 'ui',
|
||||
'Int32': 'l',
|
||||
'Uint32': 'ul',
|
||||
'Float32': 'f'
|
||||
};
|
||||
|
||||
const arraysWithStructAccessors = [];
|
||||
const arrayTypeEntries = new Set();
|
||||
const layoutCache = {};
|
||||
|
||||
function normalizeMembers(members, usedTypes) {
|
||||
return members.map((member) => {
|
||||
if (usedTypes && !usedTypes.has(member.type)) {
|
||||
usedTypes.add(member.type);
|
||||
}
|
||||
|
||||
return util.extend(member, {
|
||||
size: sizeOf(member.type),
|
||||
view: member.type.toLowerCase()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// - If necessary, write the StructArrayLayout_* class for the given layout
|
||||
// - If `includeStructAccessors`, write the fancy subclass
|
||||
// - Add an entry for `name` in the array type registry
|
||||
function createStructArrayType(name: string, layout: StructArrayLayout, includeStructAccessors: boolean = false) {
|
||||
const hasAnchorPoint = layout.members.some(m => m.name === 'anchorPointX');
|
||||
|
||||
// create the underlying StructArrayLayout class exists
|
||||
const layoutClass = createStructArrayLayoutType(layout);
|
||||
const arrayClass = `${camelize(name)}Array`;
|
||||
|
||||
if (includeStructAccessors) {
|
||||
const usedTypes = new Set(['Uint8']);
|
||||
const members = normalizeMembers(layout.members, usedTypes);
|
||||
arraysWithStructAccessors.push({
|
||||
arrayClass,
|
||||
members,
|
||||
size: layout.size,
|
||||
usedTypes,
|
||||
hasAnchorPoint,
|
||||
layoutClass,
|
||||
includeStructAccessors
|
||||
});
|
||||
} else {
|
||||
arrayTypeEntries.add(`export class ${arrayClass} extends ${layoutClass} {}`);
|
||||
}
|
||||
}
|
||||
|
||||
function createStructArrayLayoutType({members, size, alignment}) {
|
||||
const usedTypes = new Set(['Uint8']);
|
||||
members = normalizeMembers(members, usedTypes);
|
||||
|
||||
// combine consecutive 'members' with same underlying type, summing their
|
||||
// component counts
|
||||
if (!alignment || alignment === 1) members = members.reduce((memo, member) => {
|
||||
if (memo.length > 0 && memo[memo.length - 1].type === member.type) {
|
||||
const last = memo[memo.length - 1];
|
||||
return memo.slice(0, -1).concat(util.extend({}, last, {
|
||||
components: last.components + member.components,
|
||||
}));
|
||||
}
|
||||
return memo.concat(member);
|
||||
}, []);
|
||||
|
||||
const key = `${members.map(m => `${m.components}${typeAbbreviations[m.type]}`).join('')}${size}`;
|
||||
const className = `StructArrayLayout${key}`;
|
||||
|
||||
if (!layoutCache[key]) {
|
||||
layoutCache[key] = {
|
||||
className,
|
||||
members,
|
||||
size,
|
||||
usedTypes
|
||||
};
|
||||
}
|
||||
|
||||
return className;
|
||||
}
|
||||
|
||||
function sizeOf(type: ViewType): number {
|
||||
return viewTypes[type].BYTES_PER_ELEMENT;
|
||||
}
|
||||
|
||||
function camelize (str) {
|
||||
return str.replace(/(?:^|[-_])(.)/g, (_, x) => {
|
||||
return /^[0-9]$/.test(x) ? _ : x.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
createStructArrayType('pos', posAttributes);
|
||||
createStructArrayType('pos3d', pos3dAttributes);
|
||||
createStructArrayType('raster_bounds', rasterBoundsAttributes);
|
||||
|
||||
// layout vertex arrays
|
||||
const layoutAttributes = {
|
||||
circle: circleAttributes,
|
||||
fill: fillAttributes,
|
||||
'fill-extrusion': fillExtrusionAttributes,
|
||||
heatmap: circleAttributes,
|
||||
line: lineLayoutAttributes,
|
||||
lineExt: lineLayoutAttributesExt,
|
||||
pattern: patternAttributes
|
||||
};
|
||||
for (const name in layoutAttributes) {
|
||||
createStructArrayType(`${name.replace(/-/g, '_')}_layout`, layoutAttributes[name]);
|
||||
}
|
||||
|
||||
createStructArrayType('symbol_layout', symbolLayoutAttributes);
|
||||
createStructArrayType('symbol_dynamic_layout', dynamicLayoutAttributes);
|
||||
createStructArrayType('symbol_opacity', placementOpacityAttributes);
|
||||
createStructArrayType('collision_box', collisionBox, true);
|
||||
createStructArrayType('collision_box_layout', collisionBoxLayout);
|
||||
createStructArrayType('collision_circle_layout', collisionCircleLayout);
|
||||
createStructArrayType('collision_vertex', collisionVertexAttributes);
|
||||
createStructArrayType('quad_triangle', quadTriangle);
|
||||
createStructArrayType('placed_symbol', placement, true);
|
||||
createStructArrayType('symbol_instance', symbolInstance, true);
|
||||
createStructArrayType('glyph_offset', glyphOffset, true);
|
||||
createStructArrayType('symbol_line_vertex', lineVertex, true);
|
||||
createStructArrayType('text_anchor_offset', textAnchorOffset, true);
|
||||
|
||||
// feature index array
|
||||
createStructArrayType('feature_index', createLayout([
|
||||
// the index of the feature in the original vectortile
|
||||
{type: 'Uint32', name: 'featureIndex'},
|
||||
// the source layer the feature appears in
|
||||
{type: 'Uint16', name: 'sourceLayerIndex'},
|
||||
// the bucket the feature appears in
|
||||
{type: 'Uint16', name: 'bucketIndex'}
|
||||
]), true);
|
||||
|
||||
// triangle index array
|
||||
createStructArrayType('triangle_index', createLayout([
|
||||
{type: 'Uint16', name: 'vertices', components: 3}
|
||||
]));
|
||||
|
||||
// line index array
|
||||
createStructArrayType('line_index', createLayout([
|
||||
{type: 'Uint16', name: 'vertices', components: 2}
|
||||
]));
|
||||
|
||||
// line strip index array
|
||||
createStructArrayType('line_strip_index', createLayout([
|
||||
{type: 'Uint16', name: 'vertices', components: 1}
|
||||
]));
|
||||
|
||||
// paint vertex arrays
|
||||
|
||||
// used by SourceBinder for float properties
|
||||
createStructArrayLayoutType(createLayout([{
|
||||
name: 'dummy name (unused for StructArrayLayout)',
|
||||
type: 'Float32',
|
||||
components: 1
|
||||
}], 4));
|
||||
|
||||
// used by SourceBinder for color properties and CompositeBinder for float properties
|
||||
createStructArrayLayoutType(createLayout([{
|
||||
name: 'dummy name (unused for StructArrayLayout)',
|
||||
type: 'Float32',
|
||||
components: 2
|
||||
}], 4));
|
||||
|
||||
// used by CompositeBinder for color properties
|
||||
createStructArrayLayoutType(createLayout([{
|
||||
name: 'dummy name (unused for StructArrayLayout)',
|
||||
type: 'Float32',
|
||||
components: 4
|
||||
}], 4));
|
||||
|
||||
const layouts = Object.keys(layoutCache).map(k => layoutCache[k]);
|
||||
|
||||
function emitStructArrayLayout(locals) {
|
||||
const output = [];
|
||||
const {
|
||||
className,
|
||||
members,
|
||||
size,
|
||||
usedTypes
|
||||
} = locals;
|
||||
const structArrayLayoutClass = className;
|
||||
|
||||
output.push(
|
||||
`/**
|
||||
* @internal
|
||||
* Implementation of the StructArray layout:`);
|
||||
|
||||
for (const member of members) {
|
||||
output.push(
|
||||
` * [${member.offset}] - ${member.type}[${member.components}]`);
|
||||
}
|
||||
|
||||
output.push(
|
||||
` *
|
||||
*/
|
||||
class ${structArrayLayoutClass} extends StructArray {`);
|
||||
|
||||
for (const type of usedTypes) {
|
||||
output.push(
|
||||
` ${type.toLowerCase()}: ${type}Array;`);
|
||||
}
|
||||
|
||||
output.push(`
|
||||
_refreshViews() {`);
|
||||
|
||||
for (const type of usedTypes) {
|
||||
output.push(
|
||||
` this.${type.toLowerCase()} = new ${type}Array(this.arrayBuffer);`);
|
||||
}
|
||||
|
||||
output.push(
|
||||
' }');
|
||||
|
||||
// prep for emplaceBack: collect type sizes and count the number of arguments
|
||||
// we'll need
|
||||
const bytesPerElement = size;
|
||||
const usedTypeSizes = [];
|
||||
const argNames = [];
|
||||
const argNamesTyped = [];
|
||||
|
||||
for (const member of members) {
|
||||
if (usedTypeSizes.indexOf(member.size) < 0) {
|
||||
usedTypeSizes.push(member.size);
|
||||
}
|
||||
for (let c = 0; c < member.components; c++) {
|
||||
// arguments v0, v1, v2, ... are, in order, the components of
|
||||
// member 0, then the components of member 1, etc.
|
||||
const name = `v${argNames.length}`;
|
||||
argNames.push(name);
|
||||
argNamesTyped.push(`${name}: number`);
|
||||
}
|
||||
}
|
||||
|
||||
output.push(
|
||||
`
|
||||
public emplaceBack(${argNamesTyped.join(', ')}) {
|
||||
const i = this.length;
|
||||
this.resize(i + 1);
|
||||
return this.emplace(i, ${argNames.join(', ')});
|
||||
}
|
||||
|
||||
public emplace(i: number, ${argNamesTyped.join(', ')}) {`);
|
||||
|
||||
for (const size of usedTypeSizes) {
|
||||
output.push(
|
||||
` const o${size.toFixed(0)} = i * ${(bytesPerElement / size).toFixed(0)};`);
|
||||
}
|
||||
|
||||
let argIndex = 0;
|
||||
for (const member of members) {
|
||||
for (let c = 0; c < member.components; c++) {
|
||||
// The index for `member` component `c` into the appropriate type array is:
|
||||
// this.{TYPE}[o{SIZE} + MEMBER_OFFSET + {c}] = v{X}
|
||||
// where MEMBER_OFFSET = ROUND(member.offset / size) is the per-element
|
||||
// offset of this member into the array
|
||||
const index = `o${member.size.toFixed(0)} + ${(member.offset / member.size + c).toFixed(0)}`;
|
||||
|
||||
output.push(
|
||||
` this.${member.view}[${index}] = v${argIndex++};`);
|
||||
}
|
||||
}
|
||||
|
||||
output.push(
|
||||
` return i;
|
||||
}
|
||||
}
|
||||
|
||||
${structArrayLayoutClass}.prototype.bytesPerElement = ${size};
|
||||
register('${structArrayLayoutClass}', ${structArrayLayoutClass});
|
||||
`);
|
||||
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
function emitStructArray(locals) {
|
||||
const output = [];
|
||||
const {
|
||||
arrayClass,
|
||||
members,
|
||||
size,
|
||||
hasAnchorPoint,
|
||||
layoutClass,
|
||||
includeStructAccessors
|
||||
} = locals;
|
||||
|
||||
const structTypeClass = arrayClass.replace('Array', 'Struct');
|
||||
const structArrayClass = arrayClass;
|
||||
const structArrayLayoutClass = layoutClass;
|
||||
|
||||
// collect components
|
||||
const components = [];
|
||||
for (const member of members) {
|
||||
for (let c = 0; c < member.components; c++) {
|
||||
let name = member.name;
|
||||
if (member.components > 1) {
|
||||
name += c;
|
||||
}
|
||||
components.push({name, member, component: c});
|
||||
}
|
||||
}
|
||||
|
||||
// exceptions for which we generate accessors on the array rather than a separate struct for performance
|
||||
const useComponentGetters = structArrayClass === 'GlyphOffsetArray' || structArrayClass === 'SymbolLineVertexArray';
|
||||
|
||||
if (includeStructAccessors && !useComponentGetters) {
|
||||
output.push(
|
||||
`/** @internal */
|
||||
class ${structTypeClass} extends Struct {
|
||||
_structArray: ${structArrayClass};`);
|
||||
|
||||
for (const {name, member, component} of components) {
|
||||
const elementOffset = `this._pos${member.size.toFixed(0)}`;
|
||||
const componentOffset = (member.offset / member.size + component).toFixed(0);
|
||||
const index = `${elementOffset} + ${componentOffset}`;
|
||||
const componentAccess = `this._structArray.${member.view}[${index}]`;
|
||||
|
||||
output.push(
|
||||
` get ${name}() { return ${componentAccess}; }`);
|
||||
|
||||
// generate setters for properties that are updated during runtime symbol placement; others are read-only
|
||||
if (name === 'crossTileID' || name === 'placedOrientation' || name === 'hidden') {
|
||||
output.push(
|
||||
` set ${name}(x: number) { ${componentAccess} = x; }`);
|
||||
}
|
||||
}
|
||||
|
||||
// Special case used for the CollisionBoxArray type
|
||||
if (hasAnchorPoint) {
|
||||
output.push(
|
||||
' get anchorPoint() { return new Point(this.anchorPointX, this.anchorPointY); }');
|
||||
}
|
||||
|
||||
output.push(
|
||||
`}
|
||||
|
||||
${structTypeClass}.prototype.size = ${size};
|
||||
|
||||
export type ${structTypeClass.replace('Struct', '')} = ${structTypeClass};
|
||||
`);
|
||||
} // end 'if (includeStructAccessors)'
|
||||
|
||||
output.push(
|
||||
`/** @internal */
|
||||
export class ${structArrayClass} extends ${structArrayLayoutClass} {`);
|
||||
|
||||
if (useComponentGetters) {
|
||||
for (const member of members) {
|
||||
for (let c = 0; c < member.components; c++) {
|
||||
if (!includeStructAccessors) continue;
|
||||
let name = `get${member.name}`;
|
||||
if (member.components > 1) {
|
||||
name += c;
|
||||
}
|
||||
const componentOffset = (member.offset / member.size + c).toFixed(0);
|
||||
const componentStride = size / member.size;
|
||||
output.push(
|
||||
` ${name}(index: number) { return this.${member.view}[index * ${componentStride} + ${componentOffset}]; }`);
|
||||
}
|
||||
}
|
||||
} else if (includeStructAccessors) { // get(i)
|
||||
output.push(
|
||||
` /**
|
||||
* Return the ${structTypeClass} at the given location in the array.
|
||||
* @param index - The index of the element.
|
||||
*/
|
||||
get(index: number): ${structTypeClass} {
|
||||
return new ${structTypeClass}(this, index);
|
||||
}`);
|
||||
}
|
||||
output.push(
|
||||
`}
|
||||
|
||||
register('${structArrayClass}', ${structArrayClass});
|
||||
`);
|
||||
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
fs.writeFileSync('src/data/array_types.g.ts',
|
||||
`// This file is generated. Edit build/generate-struct-arrays.ts, then run \`npm run codegen\`.
|
||||
|
||||
import {Struct, StructArray} from '../util/struct_array';
|
||||
import {register} from '../util/web_worker_transfer';
|
||||
import Point from '@mapbox/point-geometry';
|
||||
|
||||
${layouts.map(emitStructArrayLayout).join('\n')}
|
||||
${arraysWithStructAccessors.map(emitStructArray).join('\n')}
|
||||
${[...arrayTypeEntries].join('\n')}
|
||||
export {
|
||||
${layouts.map(layout => layout.className).join(',\n ')}
|
||||
};
|
||||
`);
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
|
||||
import {v8} from '@maplibre/maplibre-gl-style-spec';
|
||||
|
||||
function camelCase(str: string): string {
|
||||
return str.replace(/-(.)/g, (_, x) => {
|
||||
return x.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
function pascalCase(str: string): string {
|
||||
const almostCamelized = camelCase(str);
|
||||
return almostCamelized[0].toUpperCase() + almostCamelized.slice(1);
|
||||
}
|
||||
|
||||
function nativeType(property) {
|
||||
switch (property.type) {
|
||||
case 'boolean':
|
||||
return 'boolean';
|
||||
case 'number':
|
||||
return 'number';
|
||||
case 'string':
|
||||
return 'string';
|
||||
case 'enum':
|
||||
return Object.keys(property.values).map(v => JSON.stringify(v)).join(' | ');
|
||||
case 'color':
|
||||
return 'Color';
|
||||
case 'padding':
|
||||
return 'Padding';
|
||||
case 'numberArray':
|
||||
return 'NumberArray';
|
||||
case 'colorArray':
|
||||
return 'ColorArray';
|
||||
case 'variableAnchorOffsetCollection':
|
||||
return 'VariableAnchorOffsetCollection';
|
||||
case 'sprite':
|
||||
return 'Sprite';
|
||||
case 'formatted':
|
||||
return 'Formatted';
|
||||
case 'resolvedImage':
|
||||
return 'ResolvedImage';
|
||||
case 'array':
|
||||
if (property.length) {
|
||||
return `[${new Array(property.length).fill(nativeType({type: property.value})).join(', ')}]`;
|
||||
} else {
|
||||
return `Array<${nativeType({type: property.value, values: property.values})}>`;
|
||||
}
|
||||
default: throw new Error(`unknown type "${property.type}" for "${property.name}"`);
|
||||
}
|
||||
}
|
||||
|
||||
function possiblyEvaluatedType(property) {
|
||||
const propType = nativeType(property);
|
||||
|
||||
switch (property['property-type']) {
|
||||
case 'color-ramp':
|
||||
return 'ColorRampProperty';
|
||||
case 'cross-faded':
|
||||
return `CrossFaded<${propType}>`;
|
||||
case 'cross-faded-data-driven':
|
||||
return `PossiblyEvaluatedPropertyValue<CrossFaded<${propType}>>`;
|
||||
case 'data-driven':
|
||||
return `PossiblyEvaluatedPropertyValue<${propType}>`;
|
||||
}
|
||||
|
||||
return propType;
|
||||
}
|
||||
|
||||
function propertyType(property) {
|
||||
switch (property['property-type']) {
|
||||
case 'data-driven':
|
||||
return `DataDrivenProperty<${nativeType(property)}>`;
|
||||
case 'cross-faded':
|
||||
return `CrossFadedProperty<${nativeType(property)}>`;
|
||||
case 'cross-faded-data-driven':
|
||||
return `CrossFadedDataDrivenProperty<${nativeType(property)}>`;
|
||||
case 'color-ramp':
|
||||
return 'ColorRampProperty';
|
||||
case 'data-constant':
|
||||
case 'constant':
|
||||
return `DataConstantProperty<${nativeType(property)}>`;
|
||||
default:
|
||||
throw new Error(`unknown property-type "${property['property-type']}" for ${property.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function runtimeType(property) {
|
||||
switch (property.type) {
|
||||
case 'boolean':
|
||||
return 'BooleanType';
|
||||
case 'number':
|
||||
return 'NumberType';
|
||||
case 'string':
|
||||
case 'enum':
|
||||
return 'StringType';
|
||||
case 'color':
|
||||
return 'ColorType';
|
||||
case 'padding':
|
||||
return 'PaddingType';
|
||||
case 'variableAnchorOffsetCollection':
|
||||
return 'VariableAnchorOffsetCollectionType';
|
||||
case 'sprite':
|
||||
return 'SpriteType';
|
||||
case 'formatted':
|
||||
return 'FormattedType';
|
||||
case 'Image':
|
||||
return 'ImageType';
|
||||
case 'array':
|
||||
if (property.length) {
|
||||
return `array(${runtimeType({type: property.value})}, ${property.length})`;
|
||||
} else {
|
||||
return `array(${runtimeType({type: property.value})})`;
|
||||
}
|
||||
default: throw new Error(`unknown type "${property.type}" for "${property.name}"`);
|
||||
}
|
||||
}
|
||||
|
||||
function overrides(property) {
|
||||
return `{ runtimeType: ${runtimeType(property)}, getOverride: (o) => o.${camelCase(property.name)}, hasOverride: (o) => !!o.${camelCase(property.name)} }`;
|
||||
}
|
||||
|
||||
function propertyValue(property, type) {
|
||||
const propertyAsSpec = `styleSpec["${type}_${property.layerType}"]["${property.name}"] as any as StylePropertySpecification`;
|
||||
|
||||
switch (property['property-type']) {
|
||||
case 'data-driven':
|
||||
if (property.overridable) {
|
||||
return `new DataDrivenProperty(${propertyAsSpec}, ${overrides(property)})`;
|
||||
} else {
|
||||
return `new DataDrivenProperty(${propertyAsSpec})`;
|
||||
}
|
||||
case 'cross-faded':
|
||||
return `new CrossFadedProperty(${propertyAsSpec})`;
|
||||
case 'cross-faded-data-driven':
|
||||
return `new CrossFadedDataDrivenProperty(${propertyAsSpec})`;
|
||||
case 'color-ramp':
|
||||
return `new ColorRampProperty(${propertyAsSpec})`;
|
||||
case 'data-constant':
|
||||
case 'constant':
|
||||
return `new DataConstantProperty(${propertyAsSpec})`;
|
||||
default:
|
||||
throw new Error(`unknown property-type "${property['property-type']}" for ${property.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
const layers = Object.keys(v8.layer.type.values).map((type) => {
|
||||
const layoutProperties = Object.keys(v8[`layout_${type}`]).reduce((memo, name) => {
|
||||
if (name !== 'visibility') {
|
||||
v8[`layout_${type}`][name].name = name;
|
||||
v8[`layout_${type}`][name].layerType = type;
|
||||
memo.push(v8[`layout_${type}`][name]);
|
||||
}
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
const paintProperties = Object.keys(v8[`paint_${type}`]).reduce((memo, name) => {
|
||||
v8[`paint_${type}`][name].name = name;
|
||||
v8[`paint_${type}`][name].layerType = type;
|
||||
memo.push(v8[`paint_${type}`][name]);
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
return {type, layoutProperties, paintProperties};
|
||||
});
|
||||
|
||||
function emitlayerProperties(locals) {
|
||||
const output = [];
|
||||
const layerType = pascalCase(locals.type);
|
||||
const {
|
||||
layoutProperties,
|
||||
paintProperties
|
||||
} = locals;
|
||||
|
||||
output.push(
|
||||
`// This file is generated. Edit build/generate-style-code.ts, then run 'npm run codegen'.
|
||||
/* eslint-disable */
|
||||
|
||||
import {latest as styleSpec} from '@maplibre/maplibre-gl-style-spec';
|
||||
|
||||
import {
|
||||
Properties,
|
||||
DataConstantProperty,
|
||||
DataDrivenProperty,
|
||||
CrossFadedDataDrivenProperty,
|
||||
CrossFadedProperty,
|
||||
ColorRampProperty,
|
||||
PossiblyEvaluatedPropertyValue,
|
||||
CrossFaded
|
||||
} from '../properties';
|
||||
|
||||
import type {Color, Formatted, Padding, NumberArray, ColorArray, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
|
||||
import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
|
||||
`);
|
||||
|
||||
const overridables = paintProperties.filter(p => p.overridable);
|
||||
if (overridables.length) {
|
||||
const overridesArray = `import {
|
||||
${overridables.reduce((imports, prop) => { imports.push(runtimeType(prop)); return imports; }, []).join(',\n ')}
|
||||
} from '@maplibre/maplibre-gl-style-spec';
|
||||
`;
|
||||
output.push(overridesArray);
|
||||
}
|
||||
|
||||
if (layoutProperties.length) {
|
||||
output.push(
|
||||
`export type ${layerType}LayoutProps = {`);
|
||||
|
||||
for (const property of layoutProperties) {
|
||||
output.push(
|
||||
` "${property.name}": ${propertyType(property)},`);
|
||||
}
|
||||
|
||||
output.push(
|
||||
`};
|
||||
|
||||
export type ${layerType}LayoutPropsPossiblyEvaluated = {`);
|
||||
|
||||
for (const property of layoutProperties) {
|
||||
output.push(
|
||||
` "${property.name}": ${possiblyEvaluatedType(property)},`);
|
||||
}
|
||||
|
||||
output.push(
|
||||
`};
|
||||
|
||||
let layout: Properties<${layerType}LayoutProps>;
|
||||
const getLayout = () => layout = layout || new Properties({`);
|
||||
|
||||
for (const property of layoutProperties) {
|
||||
output.push(
|
||||
` "${property.name}": ${propertyValue(property, 'layout')},`);
|
||||
}
|
||||
|
||||
output.push(
|
||||
'});');
|
||||
}
|
||||
|
||||
if (paintProperties.length) {
|
||||
output.push(
|
||||
`
|
||||
export type ${layerType}PaintProps = {`);
|
||||
|
||||
for (const property of paintProperties) {
|
||||
output.push(
|
||||
` "${property.name}": ${propertyType(property)},`);
|
||||
}
|
||||
|
||||
output.push(
|
||||
`};
|
||||
|
||||
export type ${layerType}PaintPropsPossiblyEvaluated = {`);
|
||||
|
||||
for (const property of paintProperties) {
|
||||
output.push(
|
||||
` "${property.name}": ${possiblyEvaluatedType(property)},`);
|
||||
}
|
||||
|
||||
output.push(
|
||||
'};');
|
||||
} else {
|
||||
output.push(
|
||||
`export type ${layerType}PaintProps = {};`);
|
||||
}
|
||||
|
||||
output.push(
|
||||
`
|
||||
let paint: Properties<${layerType}PaintProps>;
|
||||
const getPaint = () => paint = paint || new Properties({`);
|
||||
|
||||
for (const property of paintProperties) {
|
||||
output.push(
|
||||
` "${property.name}": ${propertyValue(property, 'paint')},`);
|
||||
}
|
||||
|
||||
output.push(
|
||||
`});
|
||||
|
||||
export default ({ get paint() { return getPaint() }${layoutProperties.length ? ', get layout() { return getLayout() }' : ''} });`);
|
||||
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
for (const layer of layers) {
|
||||
fs.writeFileSync(`src/style/style_layer/${layer.type.replace('-', '_')}_style_layer_properties.g.ts`, emitlayerProperties(layer));
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Build Scripts
|
||||
|
||||
This folder holds common build scripts accessed via the various `npm run` commands.
|
||||
Codegen is executed when calling `npm install` in order to generate all artifacts needed for the build to pass
|
||||
|
||||
## Bundeling all the code
|
||||
|
||||
The bundling process can be split into several steps:
|
||||
|
||||
`npm run build-css`
|
||||
This command will compile the css code and create the css file.
|
||||
|
||||
`npm run build-prod` and `npm run build-dev`
|
||||
These commands will use rollup to bundle the code. This is where the magic happens and uses some files in this folder.
|
||||
|
||||
`banner.ts` is used to create a banner at the beginning of the output file
|
||||
|
||||
`rollup_plugins.ts` is used to define common plugins for rollup configurations
|
||||
|
||||
`rollup_plugin_minify_style_spec.ts` is used to specify the plugin used in style spec bundeling
|
||||
|
||||
In the `rollup` folder there are some files that are used as linking files as they link to other files for rollup to pick when bundling.
|
||||
|
||||
Rollup is generating 3 files throughout the process of bundling:
|
||||
|
||||
`index.ts` a file containing all the code that will run in the main thread.
|
||||
|
||||
`shared.ts` a file containing all the code shared between the main and worker code.
|
||||
|
||||
`worker.ts` a file containing all the code the will run in the worker threads.
|
||||
|
||||
These 3 files are then referenced and used by the `bundle_prelude.js` file. It allows loading the web worker code automatically in web workers without any extra effort from someone who would like to use the library, i.e. it simply works.
|
||||
|
||||
<hr>
|
||||
|
||||
### `npm run codegen`
|
||||
|
||||
The `codegen` command runs the following three scripts, to update the corresponding code files based on the `v8.json` style source, and other data files. Contributors should run this command manually when the underlying style data is modified. The generated code files are then committed to the repo.
|
||||
|
||||
#### generate-struct-arrays.ts
|
||||
|
||||
Generates `data/array_types.ts`, which consists of:
|
||||
|
||||
- `StructArrayLayout_*` subclasses, one for each underlying memory layout
|
||||
- Named exports mapping each conceptual array type (e.g., `CircleLayoutArray`) to its corresponding `StructArrayLayout` class
|
||||
- Specific named `StructArray` subclasses, when type-specific struct accessors are needed (e.g., `CollisionBoxArray`)
|
||||
|
||||
#### generate-style-code.ts
|
||||
|
||||
Generates the various `style/style_layer/[layer type]_style_layer_properties.ts` code files based on the content of `v8.json`. These files provide the type signatures for the paint and layout properties for each type of style layer.
|
||||
|
||||
<hr>
|
||||
|
||||
### Generate Release Nodes
|
||||
|
||||
`release-notes.js` Used to generate release notes when releasing a new version
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as fs from 'fs';
|
||||
import semver from 'semver';
|
||||
|
||||
const changelogPath = 'CHANGELOG.md';
|
||||
const changelog = fs.readFileSync(changelogPath, 'utf8');
|
||||
|
||||
/*
|
||||
Parse the raw changelog text and split it into individual releases.
|
||||
|
||||
This regular expression:
|
||||
- Matches lines starting with "## x.x.x".
|
||||
- Groups the version number.
|
||||
- Skips the (optional) release date.
|
||||
- Groups the changelog content.
|
||||
- Ends when another "## x.x.x" is found.
|
||||
*/
|
||||
const regex = /^## (\d+\.\d+\.\d+.*?)\n(.+?)(?=\n^## \d+\.\d+\.\d+.*?\n)/gms;
|
||||
|
||||
let releaseNotes = [];
|
||||
let match;
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (match = regex.exec(changelog)) {
|
||||
releaseNotes.push({
|
||||
'version': match[1],
|
||||
'changelog': match[2].trim(),
|
||||
});
|
||||
}
|
||||
|
||||
const latest = releaseNotes[0];
|
||||
const previous = releaseNotes[1];
|
||||
|
||||
|
||||
// Print the release notes template.
|
||||
|
||||
const templatedReleaseNotes = `https://github.com/maplibre/maplibre-gl-js
|
||||
[Changes](https://github.com/maplibre/maplibre-gl-js/compare/v${previous.version}...v${latest.version}) since [MapLibre GL JS v${previous.version}](https://github.com/maplibre/maplibre-gl-js/releases/tag/v${previous.version}):
|
||||
|
||||
${latest.changelog}
|
||||
|
||||
${semver.prerelease(latest.version) ? 'Pre-release version' : ''}`;
|
||||
|
||||
// eslint-disable-next-line eol-last
|
||||
process.stdout.write(templatedReleaseNotes.trimEnd());
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/* eslint-disable */
|
||||
|
||||
var maplibregl = {};
|
||||
var modules = {};
|
||||
function define(moduleName, _dependencies, moduleFactory) {
|
||||
modules[moduleName] = moduleFactory;
|
||||
|
||||
// to get the list of modules see generated dist/maplibre-gl-dev.js file (look for `define(` calls)
|
||||
if (moduleName !== 'index') {
|
||||
return;
|
||||
}
|
||||
|
||||
// we assume that when an index module is initializing then other modules are loaded already
|
||||
var workerBundleString = 'var sharedModule = {}; (' + modules.shared + ')(sharedModule); (' + modules.worker + ')(sharedModule);'
|
||||
|
||||
var sharedModule = {};
|
||||
// the order of arguments of a module factory depends on rollup (it decides who is whose dependency)
|
||||
// to check the correct order, see dist/maplibre-gl-dev.js file (look for `define(` calls)
|
||||
// we assume that for our 3 chunks it will generate 3 modules and their order is predefined like the following
|
||||
modules.shared(sharedModule);
|
||||
modules.index(maplibregl, sharedModule);
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
maplibregl.setWorkerUrl(window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' })));
|
||||
}
|
||||
|
||||
return maplibregl;
|
||||
};
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Our custom intro provides a specialized "define()" function, called by the
|
||||
// AMD modules below, that sets up the worker blob URL and then executes the
|
||||
// main module, storing its exported value as 'maplibregl'
|
||||
|
||||
// The three "chunks" imported here are produced by a first Rollup pass,
|
||||
// which outputs them as AMD modules.
|
||||
|
||||
// Shared dependencies
|
||||
import '../../staging/maplibregl/shared';
|
||||
|
||||
// Worker and its unique dependencies
|
||||
// When this wrapper function is passed to our custom define() in build/rollup/bundle_prelude.js,
|
||||
// it gets stringified, together with the shared wrapper (using
|
||||
// Function.toString()), and the resulting string of code is made into a
|
||||
// Blob URL that gets used by the main module to create the web workers.
|
||||
import '../../staging/maplibregl/worker';
|
||||
|
||||
// Main module and its dependencies
|
||||
import '../../staging/maplibregl/index';
|
||||
|
||||
export default maplibregl;
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import strip from '@rollup/plugin-strip';
|
||||
import {type Plugin} from 'rollup';
|
||||
import json from '@rollup/plugin-json';
|
||||
import {visualizer} from 'rollup-plugin-visualizer';
|
||||
|
||||
const {BUNDLE} = process.env;
|
||||
const stats = BUNDLE === 'stats';
|
||||
|
||||
// Common set of plugins/transformations shared across different rollup
|
||||
// builds (main maplibre bundle, style-spec package, benchmarks bundle)
|
||||
|
||||
export const nodeResolve = resolve({
|
||||
browser: true,
|
||||
preferBuiltins: false
|
||||
});
|
||||
|
||||
export const plugins = (production: boolean): Plugin[] => [
|
||||
json(),
|
||||
// https://github.com/zaach/jison/issues/351
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
include: /\/jsonlint-lines-primitives\/lib\/jsonlint.js/,
|
||||
delimiters: ['', ''],
|
||||
values: {
|
||||
'_token_stack:': ''
|
||||
}
|
||||
}),
|
||||
production && strip({
|
||||
sourceMap: true,
|
||||
functions: ['PerformanceUtils.*']
|
||||
}),
|
||||
production && terser({
|
||||
compress: {
|
||||
pure_getters: true,
|
||||
passes: 3
|
||||
},
|
||||
sourceMap: true
|
||||
}),
|
||||
nodeResolve,
|
||||
typescript(),
|
||||
commonjs({
|
||||
// global keyword handling causes Webpack compatibility issues, so we disabled it:
|
||||
// https://github.com/mapbox/mapbox-gl-js/pull/6956
|
||||
ignoreGlobal: true
|
||||
}),
|
||||
// generate bundle stats in multiple formats (treemap, sunburst, etc...)
|
||||
...(stats ? (['treemap', 'sunburst', 'flamegraph', 'network'] as const).map(template =>
|
||||
visualizer({
|
||||
template: template,
|
||||
title: `gl-js-${template}`,
|
||||
filename: `staging/${template}.html`,
|
||||
gzipSize: true,
|
||||
brotliSize: true,
|
||||
sourcemap: true,
|
||||
open: true
|
||||
})
|
||||
) : [])
|
||||
].filter(Boolean) as Plugin[];
|
||||
|
||||
export const watchStagingPlugin: Plugin = {
|
||||
name: 'watch-external',
|
||||
buildStart() {
|
||||
this.addWatchFile('staging/maplibregl/index.js');
|
||||
this.addWatchFile('staging/maplibregl/shared.js');
|
||||
this.addWatchFile('staging/maplibregl/worker.js');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user