浏览代码

Put projection transformations to d3

Let d3 handle the centering of the projections to the specified bounds.

Also start working on consuming bbox data.
master
父节点
当前提交
567ae8be0a
共有 12 个文件被更改,包括 148799 次插入210 次删除
  1. 二进制
      __fixtures__/biomes-test.png
  2. +3
    -1
      package.json
  3. +52
    -0
      scripts/generate-bounding-box-data/index.ts
  4. +260
    -0
      scripts/generate-bounding-box-data/ne_10m_admin_0_countries.json
  5. +148051
    -0
      scripts/generate-bounding-box-data/sample-russia.json
  6. +3
    -0
      src/geo.ts
  7. +2
    -207
      src/index.ts
  8. +15
    -0
      src/png.ts
  9. +302
    -0
      src/projection.ts
  10. +2
    -1
      tsconfig.json
  11. +22
    -0
      tsconfig.script.json
  12. +87
    -1
      yarn.lock

二进制
__fixtures__/biomes-test.png 查看文件

之前 之后
宽度: 461  |  高度: 231  |  大小: 32 KiB

+ 3
- 1
package.json 查看文件

@@ -59,6 +59,7 @@
"eslint-config-lxsmnsyc": "^0.4.0",
"fast-check": "^2.24.0",
"pridepack": "1.1.0",
"ts-node": "^10.7.0",
"tslib": "^2.3.1",
"typescript": "^4.5.4",
"vitest": "^0.2.5"
@@ -72,7 +73,8 @@
"watch": "pridepack watch",
"start": "pridepack start",
"dev": "pridepack dev",
"test": "vitest"
"test": "vitest",
"generate-bounding-box-data": "ts-node --project tsconfig.script.json scripts/generate-bounding-box-data/index.ts"
},
"private": false,
"description": "Utility for map projections.",


+ 52
- 0
scripts/generate-bounding-box-data/index.ts 查看文件

@@ -0,0 +1,52 @@
import { readFile, writeFile } from 'fs/promises';

// const getBoundingBox = (coordinates: any) => {
// return coordinates.reduce(
// (coordinatesBoundingBox, polygonGroup) => {
// const pg = polygonGroup.reduce(
// (polygonGroupBoundingBox, polygon) => {
// return [
// Math.min()
// ]
// },
// coordinatesBoundingBox
// )
// },
// [[180, -90], [-180, 90]]
// )
// }

const main = async () => {
let json;
try {
const jsonBuffer = await readFile('scripts/generate-bounding-box-data/ne_10m_admin_0_countries.json');
const jsonString = jsonBuffer.toString('utf-8');
json = JSON.parse(jsonString);
} catch {
process.stderr.write('Unable to read Admin 0 Countries GeoJSON. Obtain data from Natural Earth and convert to GeoJSON.');
process.exit(1);
return;
}

const recognizedCountries = json.features.filter(f => f.properties['ISO_A2_EH'] !== '-99');
await writeFile(
'scripts/generate-bounding-box-data/sample-russia.json',
JSON.stringify(
json.features.find(f => f.properties['NAME'] === 'Russia'),
null,
2
)
);

const countries = recognizedCountries.map(f => ({
name: f.properties['NAME'],
countryCode: f.properties['ISO_A2_EH'],
}))
.sort((a, b) => a.name.localeCompare(b.name));

countries.forEach(c => {
console.log(c);
})
}

void main();

+ 260
- 0
scripts/generate-bounding-box-data/ne_10m_admin_0_countries.json
文件差异内容过多而无法显示
查看文件


+ 148051
- 0
scripts/generate-bounding-box-data/sample-russia.json
文件差异内容过多而无法显示
查看文件


+ 3
- 0
src/geo.ts 查看文件

@@ -0,0 +1,3 @@
export const getBounds = () => {

}

+ 2
- 207
src/index.ts 查看文件

@@ -1,207 +1,2 @@
import * as d3geo from 'd3-geo';
import * as d3geoProjection from 'd3-geo-projection';
import * as d3geoPolygon from 'd3-geo-polygon';
import { PNG } from 'pngjs';
import { readFile } from 'fs/promises';

type Coords = [number, number]
type Bounds = [Coords, Coords]

type ProjectionFunction = (...args: unknown[]) => d3geo.GeoProjection;

const referenceWidth = 960 as const;
const referenceHeight = 480 as const;

type ProjectOptions = {
inputDimensions?: { width: number, height: number },
bounds?: Bounds,
wrapAround?: boolean,
}

type Projection = [string, ...unknown[]]

const getCenter = (bounds: Bounds): Coords => {
const [nw, se] = bounds;
const [nwLng, nwLat] = nw;
const [seLng, seLat] = se;
return [
// TODO: better center checking that wraps longitudes
(nwLng + seLng) / 2,
(nwLat + seLat) / 2,
];
};

const getProjectionFunctionSource = (projectionFunctionName: string): Record<string, unknown> => {
if (projectionFunctionName in d3geoPolygon) {
return d3geoPolygon as Record<string, unknown>;
}
if (projectionFunctionName in d3geoProjection) {
return d3geoProjection as Record<string, unknown>;
}
if (projectionFunctionName in d3geo) {
return d3geo as Record<string, unknown>;
}

throw new Error(`Unknown projection: ${projectionFunctionName}`);
};

const buildProjectionFunction = (projection: Projection, options: ProjectOptions = {}) => {
const [projectionName, ...projectionArgs] = projection;
const projectionFunctionName = `geo${projectionName}`;
const geoProjectionSource = getProjectionFunctionSource(projectionFunctionName);
const projectionFn = geoProjectionSource[projectionFunctionName] as ProjectionFunction;
const baseProjection = projectionFn(...projectionArgs) as unknown as d3geo.GeoProjection;
const {
bounds = [[-180, 90], [180, -90]],
} = options;

return baseProjection.center(getCenter(bounds));
};

export const projectPoint = (
point: [number, number],
projection: Projection,
options: ProjectOptions = {},
): [number, number] | null => {
const [x, y] = point;

const [projectionName] = projection;
if (projectionName === 'Equirectangular') {
return point;
}

const {
inputDimensions = { width: referenceWidth, height: referenceHeight },
wrapAround = false,
} = options;

const projectionFunction = buildProjectionFunction(projection, options);
if (!projectionFunction.invert) {
return null;
}

const { width: sx, height: sy } = inputDimensions;
const originalPoint = [
(x / sx) * referenceWidth,
(y / sy) * referenceHeight,
] as [number, number];
const inverted = projectionFunction.invert(originalPoint);

if (!inverted) {
return null;
}

if (wrapAround) {
return inverted;
}

const validation = projectionFunction(inverted);
if (!validation) {
return null;
}

// https://stackoverflow.com/questions/41832043/how-to-fix-map-boundaries-on-d3-cartographic-raster-reprojection/41856996#41856996
const tolerance = 0.5;

if (
Math.abs(validation[0] - originalPoint[0]) < tolerance
&& Math.abs(validation[1] - originalPoint[1]) < tolerance
) {
// to avoid wrapping, let's only pick the points that inverts back to its original point
return inverted;
}

return null;
};

const retrievePngData = async (pngInput: string | Buffer | PNG) => {
if (typeof pngInput === 'string') {
const pngFile = await readFile(pngInput);
return PNG.sync.read(pngFile);
}

if (typeof pngInput === 'object') {
return pngInput instanceof PNG ? pngInput : PNG.sync.read(pngInput);
}

throw new TypeError('Invalid input argument.');
};

const prepareOutputData = (
projection: Projection,
sourcePng: { width: number, height: number },
options = {} as ProjectOptions,
): PNG => {
const p = buildProjectionFunction(projection, options);
const {
bounds = [[-180, 90], [180, -90]],
} = options;
const nwBoundsRaw = p(bounds[0]);
const seBoundsRaw = p(bounds[1]);
const nwBounds = nwBoundsRaw ? [
(nwBoundsRaw[0] / referenceWidth) * sourcePng.width,
(nwBoundsRaw[1] / referenceHeight) * sourcePng.height,
] : null;

const seBounds = seBoundsRaw ? [
(seBoundsRaw[0] / referenceWidth) * sourcePng.width,
(seBoundsRaw[1] / referenceHeight) * sourcePng.height,
] : null;

let tx = sourcePng.width;
let ty = sourcePng.height;
if (seBounds && nwBounds) {
tx = (nwBounds[0] + seBounds[0]);
ty = (nwBounds[1] + seBounds[1]);

if (
projection[0] === 'Mercator'
&& ty > tx
) {
ty = tx;
}
}

return new PNG({
width: tx,
height: ty,
});
};

export const project = async (
pngInput: string | Buffer | PNG,
projection: Projection,
options?: ProjectOptions,
) => {
const sourcePngData = await retrievePngData(pngInput);
const { width: sx, height: sy, data: sourceData } = sourcePngData;
const outputPngData = prepareOutputData(projection, sourcePngData, options);
const { width: tx, height: ty, data: targetData } = outputPngData;

let i = 0;
for (let y = (sy - ty) / 2; y < (ty + ((sy - ty) / 2)); y += 1) {
for (let x = 0; x < (tx + ((sx - tx) / 2)); x += 1) {
const projected = projectPoint([x, y], projection, {
bounds: options?.bounds,
inputDimensions: {
width: sx,
height: sy,
},
});
if (projected) {
const [lambda, phi] = projected;
if (!(lambda > 180 || lambda < -180 || phi > 90 || phi < -90)) {
// eslint-disable-next-line no-bitwise
const q = (((90 - phi) / 180) * sy | 0) * sx + (((180 + lambda) / 360) * sx | 0) << 2;
targetData[i] = sourceData[q];
targetData[i + 1] = sourceData[q + 1];
targetData[i + 2] = sourceData[q + 2];
targetData[i + 3] = sourceData[q + 3];
}
}
i += 4;
}
}

return PNG.sync.write(outputPngData);
};
export * from './projection';
export * from './geo';

+ 15
- 0
src/png.ts 查看文件

@@ -0,0 +1,15 @@
import { PNG } from 'pngjs';
import { readFile } from 'fs/promises';

export const load = async (pngInput: string | Buffer | PNG) => {
if (typeof pngInput === 'string') {
const pngFile = await readFile(pngInput);
return PNG.sync.read(pngFile);
}

if (typeof pngInput === 'object') {
return pngInput instanceof PNG ? pngInput : PNG.sync.read(pngInput);
}

throw new TypeError('Invalid input argument.');
};

+ 302
- 0
src/projection.ts 查看文件

@@ -0,0 +1,302 @@
import * as d3geo from 'd3-geo';
import * as d3geoProjection from 'd3-geo-projection';
import * as d3geoPolygon from 'd3-geo-polygon';
import { PNG } from 'pngjs';
import * as Png from './png';

export type Point = [number, number]
export type Bounds = [Point, Point]

type Projection = [string, ...unknown[]]

type Rect = {
width: number,
height: number,
}

export type ProjectOptions = {
bounds: Bounds,
wrapAround: boolean,
outputSize?: Partial<Rect>,
outputPadding?: {
x?: number,
y?: number,
}
}

const geoProjectionNamePrefix = 'geo';

type GeoProjectionFactory = any

const getProjectionFunction = (projectionFunctionName: string): GeoProjectionFactory => {
if (projectionFunctionName in d3geoPolygon) {
return (d3geoPolygon as Record<string, unknown>)[projectionFunctionName];
}
if (projectionFunctionName in d3geoProjection) {
return (d3geoProjection as Record<string, unknown>)[projectionFunctionName];
}
if (projectionFunctionName in d3geo) {
return (d3geo as Record<string, unknown>)[projectionFunctionName];
}

const properProjectionName = projectionFunctionName.slice(
projectionFunctionName.indexOf(geoProjectionNamePrefix)
+ geoProjectionNamePrefix.length,
);

throw new Error(`Unknown projection: ${properProjectionName}`);
};

const buildProjection = (projection: Projection) => {
const [projectionName, ...projectionArgs] = projection;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const projectionFunction = getProjectionFunction(`${geoProjectionNamePrefix}${projectionName}`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return projectionFunction(projectionArgs) as d3geo.GeoProjection;
};

const getCenter = (bounds: Bounds): Point => {
const [nw, se] = bounds;
const [nwLng, nwLat] = nw;
const [seLng, seLat] = se;
let seLngNorm = seLng;

while (seLngNorm < nwLng) {
seLngNorm += 360;
}

return [
((((nwLng + 180) + (seLngNorm + 180)) / 2) % 360) - 180,
(nwLat + seLat) / 2,
];
};

const transformGeoProjection = (
geoProjection: d3geo.GeoProjection,
options: ProjectOptions,
inputWidth: number,
) => {
const center = getCenter(options.bounds);
const transformedGeoProjection = geoProjection
.reflectX(false)
.reflectY(false)
.rotate([-center[0], -center[1]]);

const boundsGeoJson: GeoJSON.Polygon = {
type: 'Polygon',
coordinates: [
[
[options.bounds[0][0], options.bounds[0][1]],
[options.bounds[1][0], options.bounds[0][1]],
[options.bounds[1][0], options.bounds[1][1]],
[options.bounds[0][0], options.bounds[1][1]],
],
],
};

if (typeof options.outputSize?.width === 'number') {
if (typeof options.outputSize.height === 'number') {
const paddingX = options.outputPadding?.x ?? 0;
const paddingY = options.outputPadding?.y ?? 0;
return transformedGeoProjection.fitExtent(
[
[paddingX, paddingY],
[options.outputSize.width - paddingX, options.outputSize.height - paddingY],
],
boundsGeoJson,
);
}
return transformedGeoProjection.fitWidth(
options.outputSize.width,
boundsGeoJson,
);
}
if (typeof options.outputSize?.height === 'number') {
return transformedGeoProjection.fitHeight(
options.outputSize.height,
boundsGeoJson,
);
}

return transformedGeoProjection.fitWidth(inputWidth, boundsGeoJson);
};

const computeLatitudeSensitiveProjectionVerticalBounds = (
transformedGeoProjection: d3geo.GeoProjection,
bounds: Bounds,
outputWidthRaw: number,
) => {
// web mercator clipping
// const maxAbsLatitude
// = ((2 * Math.atan(Math.E ** Math.PI) - (Math.PI / 2)) / (Math.PI * 2)) * 360;
const maxAbsLatitude = 85.0511287798066;
const adjustedNwBoundsRaw = transformedGeoProjection([
bounds[0][0],
Math.min(bounds[0][1], maxAbsLatitude),
]);
const adjustedSeBoundsRaw = transformedGeoProjection([
bounds[1][0],
Math.max(bounds[1][1], -maxAbsLatitude),
]);

if (!(adjustedNwBoundsRaw && adjustedSeBoundsRaw)) {
return null;
}

return [
Math.round(outputWidthRaw),
Math.round(adjustedSeBoundsRaw[1] - adjustedNwBoundsRaw[1]),
];
};

const computeLongitudeSensitiveProjectionHorizontalBounds = (
transformedGeoProjection: d3geo.GeoProjection,
bounds: Bounds,
outputHeightRaw: number,
) => {
const adjustedWBoundsRaw = transformedGeoProjection([
bounds[0][0],
(bounds[0][1] + bounds[1][1]) / 2,
]);
const adjustedEBoundsRaw = transformedGeoProjection([
bounds[1][0],
(bounds[0][1] + bounds[1][1]) / 2,
]);

if (!(adjustedWBoundsRaw && adjustedEBoundsRaw)) {
return null;
}

return [
Math.round(adjustedEBoundsRaw[0] - adjustedWBoundsRaw[0]),
Math.round(outputHeightRaw),
];
};

const computeOutputBounds = (
transformedGeoProjection: d3geo.GeoProjection,
projectionName: string,
bounds: Bounds,
outputSize?: Partial<Rect>,
) => {
// TODO: what would it be for polygonal projections?

if (
typeof outputSize?.width === 'number'
&& typeof outputSize.height === 'number'
) {
return [
outputSize.width,
outputSize.height,
];
}

const nwBoundsRaw = transformedGeoProjection(bounds[0]);
const seBoundsRaw = transformedGeoProjection(bounds[1]);

if (!(nwBoundsRaw && seBoundsRaw)) {
return null;
}

const outputWidthRaw = seBoundsRaw[0] - nwBoundsRaw[0];
const outputHeightRaw = seBoundsRaw[1] - nwBoundsRaw[1];

switch (projectionName) {
case 'Mercator':
return computeLatitudeSensitiveProjectionVerticalBounds(
transformedGeoProjection,
bounds,
outputWidthRaw,
);
case 'Robinson':
case 'Sinusoidal':
return computeLongitudeSensitiveProjectionHorizontalBounds(
transformedGeoProjection,
bounds,
outputHeightRaw,
);
default:
break;
}

return [
Math.round(outputWidthRaw),
Math.round(outputHeightRaw),
];
};

export const project = async (
pngInput: string | Buffer | PNG,
projection: Projection,
options = {
wrapAround: false,
bounds: [[-180, 90], [180, -90]],
outputSize: undefined,
} as ProjectOptions,
) => {
const inputPng = await Png.load(pngInput);
const geoProjection = buildProjection(projection);
const [projectionName] = projection;

const transformedGeoProjection = transformGeoProjection(geoProjection, options, inputPng.width);
if (!transformedGeoProjection.invert) {
throw new Error(`No invert() function for projection "${projectionName}"`);
}

const outputBounds = computeOutputBounds(
transformedGeoProjection,
projectionName,
options.bounds,
options.outputSize,
);

if (!outputBounds) {
throw new Error(
`Cannot compute bounds, possibly unimplemented. Check logic of "${projectionName}" projection.`,
);
}

const [outputWidth, outputHeight] = outputBounds;
// outputWidth = options.outputSize?.width ?? 461;
// outputHeight = options.outputSize?.height ?? 461;

const { data: inputData } = inputPng;
const outputPng = new PNG({
width: outputWidth,
height: outputHeight,
});
const { data: outputData } = outputPng;

let i = 0;
for (let y = 0; y < outputHeight; y += 1) {
for (let x = 0; x < outputWidth; x += 1) {
const projected = transformedGeoProjection.invert([x, y]);
if (projected) {
const reversed = transformedGeoProjection(projected);
if (reversed) {
const isSamePoint = (
Math.abs(reversed[0] - x) < 0.5
&& Math.abs(reversed[1] - y) < 0.5
);

if (isSamePoint) {
const [lambda, phi] = projected;
if (!(lambda > 180 || lambda < -180 || phi > 90 || phi < -90)) {
// eslint-disable-next-line no-bitwise
const q = (((90 - phi) / 180) * inputPng.height | 0) * inputPng.width
// eslint-disable-next-line no-bitwise
+ (((180 + lambda) / 360) * inputPng.width | 0) << 2;
outputData[i] = inputData[q];
outputData[i + 1] = inputData[q + 1];
outputData[i + 2] = inputData[q + 2];
outputData[i + 3] = inputData[q + 3];
}
}
}
}
i += 4;
}
}

return PNG.sync.write(outputPng);
};

+ 2
- 1
tsconfig.json 查看文件

@@ -16,6 +16,7 @@
"moduleResolution": "node",
"jsx": "react",
"esModuleInterop": true,
"target": "ES2017"
"target": "ES2017",
"resolveJsonModule": true
}
}

+ 22
- 0
tsconfig.script.json 查看文件

@@ -0,0 +1,22 @@
{
"exclude": ["node_modules"],
"include": ["src", "types"],
"compilerOptions": {
"module": "CommonJS",
"lib": ["ESNext", "DOM"],
"importHelpers": true,
"declaration": true,
"sourceMap": true,
"rootDir": "./src",
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"jsx": "react",
"esModuleInterop": true,
"target": "ES2017",
"resolveJsonModule": true
}
}

+ 87
- 1
yarn.lock 查看文件

@@ -234,6 +234,18 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==
"@cspotcode/source-map-support@0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5"
integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==
dependencies:
"@cspotcode/source-map-consumer" "0.8.0"
"@eslint/eslintrc@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6"
@@ -324,6 +336,26 @@
resolved "https://registry.yarnpkg.com/@ovyerus/licenses/-/licenses-6.4.4.tgz#596e3ace46ab7c70bcf0e2b17f259796a4bedf9f"
integrity sha512-IHjc31WXciQT3hfvdY+M59jBkQp70Fpr04tNDVO5rez2PNv4u8tE6w//CkU+GeBoO9k2ahneSqzjzvlgjyjkGw==
"@tsconfig/node10@^1.0.7":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==
"@tsconfig/node12@^1.0.7":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c"
integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==
"@tsconfig/node14@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2"
integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==
"@tsconfig/node16@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==
"@types/chai-subset@^1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
@@ -672,7 +704,12 @@ acorn-jsx@^5.3.1:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.7.0:
acorn-walk@^8.1.1:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@^8.4.1, acorn@^8.7.0:
version "8.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
@@ -729,6 +766,11 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@@ -1075,6 +1117,11 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -1460,6 +1507,11 @@ detect-libc@^2.0.0, detect-libc@^2.0.1:
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd"
integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@@ -2694,6 +2746,11 @@ make-dir@^3.0.0:
dependencies:
semver "^6.0.0"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -3556,6 +3613,25 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
ts-node@^10.7.0:
version "10.7.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5"
integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==
dependencies:
"@cspotcode/source-map-support" "0.7.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
"@tsconfig/node16" "^1.0.2"
acorn "^8.4.1"
acorn-walk "^8.1.1"
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
v8-compile-cache-lib "^3.0.0"
yn "3.1.1"
tsconfig-paths@^3.14.1:
version "3.14.1"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
@@ -3653,6 +3729,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
v8-compile-cache-lib@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8"
integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==
v8-compile-cache@^2.0.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
@@ -3839,6 +3920,11 @@ yargs@^17.2.1:
y18n "^5.0.5"
yargs-parser "^21.0.0"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"


正在加载...
取消
保存