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", | "eslint-config-lxsmnsyc": "^0.4.0", | ||||
"fast-check": "^2.24.0", | "fast-check": "^2.24.0", | ||||
"pridepack": "1.1.0", | "pridepack": "1.1.0", | ||||
"ts-node": "^10.7.0", | |||||
"tslib": "^2.3.1", | "tslib": "^2.3.1", | ||||
"typescript": "^4.5.4", | "typescript": "^4.5.4", | ||||
"vitest": "^0.2.5" | "vitest": "^0.2.5" | ||||
@@ -72,7 +73,8 @@ | |||||
"watch": "pridepack watch", | "watch": "pridepack watch", | ||||
"start": "pridepack start", | "start": "pridepack start", | ||||
"dev": "pridepack dev", | "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, | "private": false, | ||||
"description": "Utility for map projections.", | "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", | "moduleResolution": "node", | ||||
"jsx": "react", | "jsx": "react", | ||||
"esModuleInterop": true, | "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" | "@babel/helper-validator-identifier" "^7.16.7" | ||||
to-fast-properties "^2.0.0" | 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": | "@eslint/eslintrc@^1.2.1": | ||||
version "1.2.1" | version "1.2.1" | ||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" | 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" | resolved "https://registry.yarnpkg.com/@ovyerus/licenses/-/licenses-6.4.4.tgz#596e3ace46ab7c70bcf0e2b17f259796a4bedf9f" | ||||
integrity sha512-IHjc31WXciQT3hfvdY+M59jBkQp70Fpr04tNDVO5rez2PNv4u8tE6w//CkU+GeBoO9k2ahneSqzjzvlgjyjkGw== | 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": | "@types/chai-subset@^1.3.3": | ||||
version "1.3.3" | version "1.3.3" | ||||
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94" | 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" | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" | ||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== | 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" | version "8.7.0" | ||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" | ||||
integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== | integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== | ||||
@@ -729,6 +766,11 @@ are-we-there-yet@~1.1.2: | |||||
delegates "^1.0.0" | delegates "^1.0.0" | ||||
readable-stream "^2.0.6" | 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: | argparse@^2.0.1: | ||||
version "2.0.1" | version "2.0.1" | ||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" | 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" | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" | ||||
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== | 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: | cross-spawn@^7.0.2, cross-spawn@^7.0.3: | ||||
version "7.0.3" | version "7.0.3" | ||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" | 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" | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" | ||||
integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== | 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: | dir-glob@^3.0.1: | ||||
version "3.0.1" | version "3.0.1" | ||||
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" | resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" | ||||
@@ -2694,6 +2746,11 @@ make-dir@^3.0.0: | |||||
dependencies: | dependencies: | ||||
semver "^6.0.0" | 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: | merge-stream@^2.0.0: | ||||
version "2.0.0" | version "2.0.0" | ||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" | 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: | dependencies: | ||||
is-number "^7.0.0" | 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: | tsconfig-paths@^3.14.1: | ||||
version "3.14.1" | version "3.14.1" | ||||
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" | 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" | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" | ||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= | 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: | v8-compile-cache@^2.0.3: | ||||
version "2.3.0" | version "2.3.0" | ||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" | 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" | y18n "^5.0.5" | ||||
yargs-parser "^21.0.0" | 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: | yocto-queue@^0.1.0: | ||||
version "0.1.0" | version "0.1.0" | ||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" | resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" | ||||