Let d3 handle the centering of the projections to the specified bounds. Also start working on consuming bbox data.master
@@ -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.", | |||
@@ -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(); |
@@ -0,0 +1,3 @@ | |||
export const getBounds = () => { | |||
} |
@@ -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'; |
@@ -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.'); | |||
}; |
@@ -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); | |||
}; |
@@ -16,6 +16,7 @@ | |||
"moduleResolution": "node", | |||
"jsx": "react", | |||
"esModuleInterop": true, | |||
"target": "ES2017" | |||
"target": "ES2017", | |||
"resolveJsonModule": true | |||
} | |||
} |
@@ -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 | |||
} | |||
} |
@@ -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" | |||