소스 검색

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. BIN
      __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

BIN
__fixtures__/biomes-test.png 파일 보기

Before After
Width: 461  |  Height: 231  |  Size: 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"


불러오는 중...
취소
저장