@nshiab/simple-data-analysis-core 0.0.20 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/class/SimpleTable.d.ts +52 -21
- package/esm/class/SimpleTable.d.ts.map +1 -1
- package/esm/class/SimpleTable.js +76 -98
- package/esm/helpers/findGeoColumn.js +1 -1
- package/esm/helpers/getProjection.d.ts.map +1 -1
- package/esm/helpers/getProjection.js +3 -0
- package/esm/helpers/hasGeometryColumn.d.ts +20 -0
- package/esm/helpers/hasGeometryColumn.d.ts.map +1 -0
- package/esm/helpers/hasGeometryColumn.js +21 -0
- package/esm/helpers/writeGeoData.d.ts +9 -0
- package/esm/helpers/writeGeoData.d.ts.map +1 -0
- package/esm/helpers/writeGeoData.js +110 -0
- package/esm/methods/fuzzyClean.d.ts +2 -2
- package/esm/methods/fuzzyClean.d.ts.map +1 -1
- package/esm/methods/fuzzyClean.js +25 -6
- package/esm/methods/fuzzyJoin.d.ts +2 -2
- package/esm/methods/fuzzyJoin.d.ts.map +1 -1
- package/esm/methods/fuzzyJoin.js +3 -3
- package/esm/methods/fuzzyJoinQuery.d.ts +1 -1
- package/esm/methods/fuzzyJoinQuery.d.ts.map +1 -1
- package/esm/methods/fuzzyJoinQuery.js +13 -3
- package/esm/methods/writeGeoDataQuery.d.ts.map +1 -1
- package/esm/methods/writeGeoDataQuery.js +4 -1
- package/package.json +1 -1
- package/script/class/SimpleTable.d.ts +52 -21
- package/script/class/SimpleTable.d.ts.map +1 -1
- package/script/class/SimpleTable.js +76 -98
- package/script/helpers/findGeoColumn.js +1 -1
- package/script/helpers/getProjection.d.ts.map +1 -1
- package/script/helpers/getProjection.js +3 -0
- package/script/helpers/hasGeometryColumn.d.ts +20 -0
- package/script/helpers/hasGeometryColumn.d.ts.map +1 -0
- package/script/helpers/hasGeometryColumn.js +24 -0
- package/script/helpers/writeGeoData.d.ts +9 -0
- package/script/helpers/writeGeoData.d.ts.map +1 -0
- package/script/helpers/writeGeoData.js +116 -0
- package/script/methods/fuzzyClean.d.ts +2 -2
- package/script/methods/fuzzyClean.d.ts.map +1 -1
- package/script/methods/fuzzyClean.js +25 -6
- package/script/methods/fuzzyJoin.d.ts +2 -2
- package/script/methods/fuzzyJoin.d.ts.map +1 -1
- package/script/methods/fuzzyJoin.js +3 -3
- package/script/methods/fuzzyJoinQuery.d.ts +1 -1
- package/script/methods/fuzzyJoinQuery.d.ts.map +1 -1
- package/script/methods/fuzzyJoinQuery.js +13 -3
- package/script/methods/writeGeoDataQuery.d.ts.map +1 -1
- package/script/methods/writeGeoDataQuery.js +4 -1
package/esm/class/SimpleTable.js
CHANGED
|
@@ -47,6 +47,7 @@ import normalizeQuery from "../methods/normalizeQuery.js";
|
|
|
47
47
|
import rollingQuery from "../methods/rollingQuery.js";
|
|
48
48
|
import distanceQuery from "../methods/distanceQuery.js";
|
|
49
49
|
import getGeoData from "../methods/getGeoData.js";
|
|
50
|
+
import writeGeoData from "../helpers/writeGeoData.js";
|
|
50
51
|
import splitSpread from "../methods/splitSpread.js";
|
|
51
52
|
import { readdirSync } from "node:fs";
|
|
52
53
|
import stringToArray from "../helpers/stringToArray.js";
|
|
@@ -54,24 +55,20 @@ import loadDataQuery from "../methods/loadDataQuery.js";
|
|
|
54
55
|
import mergeOptions from "../helpers/mergeOptions.js";
|
|
55
56
|
import queryDB from "../helpers/queryDB.js";
|
|
56
57
|
import writeDataQuery from "../methods/writeDataQuery.js";
|
|
57
|
-
import writeGeoDataQuery from "../methods/writeGeoDataQuery.js";
|
|
58
58
|
import runQuery from "../helpers/runQuery.js";
|
|
59
59
|
import aggregateGeoQuery from "../methods/aggregateGeoQuery.js";
|
|
60
60
|
import summarize from "../methods/summarize.js";
|
|
61
61
|
import correlations from "../methods/correlations.js";
|
|
62
62
|
import linearRegressions from "../methods/linearRegressions.js";
|
|
63
63
|
import joinGeo from "../methods/joinGeo.js";
|
|
64
|
-
import shouldFlipBeforeExport from "../helpers/shouldFlipBeforeExport.js";
|
|
65
64
|
import getProjection from "../helpers/getProjection.js";
|
|
66
65
|
import cache from "../methods/cache.js";
|
|
67
66
|
import camelCase from "../helpers/camelCase.js";
|
|
68
67
|
import formatNumber from "../helpers/formatNumber.js";
|
|
69
68
|
import createDirectory from "../helpers/createDirectory.js";
|
|
70
|
-
import rewind from "../helpers/rewind.js";
|
|
71
69
|
import writeDataAsArrays from "../helpers/writeDataAsArrays.js";
|
|
72
70
|
import logData from "../helpers/logData.js";
|
|
73
71
|
import fill from "../methods/fill.js";
|
|
74
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
75
72
|
import loadArray from "../methods/loadArray.js";
|
|
76
73
|
import cleanPath from "../helpers/cleanPath.js";
|
|
77
74
|
import Simple from "./Simple.js";
|
|
@@ -88,10 +85,9 @@ import capitalizeQuery from "../methods/capitalizeQuery.js";
|
|
|
88
85
|
import truncateQuery from "../methods/truncateQuery.js";
|
|
89
86
|
import padQuery from "../methods/padQuery.js";
|
|
90
87
|
import getProjectionParquet from "../helpers/getProjectionParquet.js";
|
|
88
|
+
import hasGeometryColumn from "../helpers/hasGeometryColumn.js";
|
|
91
89
|
import unifyColumns from "../helpers/unifyColumns.js";
|
|
92
90
|
import accumulateQuery from "../helpers/accumulateQuery.js";
|
|
93
|
-
import stringifyDates from "../helpers/stringifyDates.js";
|
|
94
|
-
import stringifyDatesInvert from "../helpers/stringifyDatesInvert.js";
|
|
95
91
|
import unnestQuery from "../helpers/unnestQuery.js";
|
|
96
92
|
import nestQuery from "../helpers/nestQuery.js";
|
|
97
93
|
import concatenateRowQuery from "../helpers/concatenateRowQuery.js";
|
|
@@ -422,7 +418,13 @@ export default class SimpleTable extends Simple {
|
|
|
422
418
|
*
|
|
423
419
|
* @example
|
|
424
420
|
* ```ts
|
|
425
|
-
* // Load geospatial data from a shapefile and reproject to WGS84
|
|
421
|
+
* // Load geospatial data from a shapefile (with relevant files in the same folder) and reproject to WGS84
|
|
422
|
+
* await table.loadGeoData("./some-data/some-data.shp", { toWGS84: true });
|
|
423
|
+
* ```
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* ```ts
|
|
427
|
+
* // Load geospatial data from a zipped shapefile and reproject to WGS84
|
|
426
428
|
* await table.loadGeoData("./some-data.shp.zip", { toWGS84: true });
|
|
427
429
|
* ```
|
|
428
430
|
*/
|
|
@@ -1903,48 +1905,57 @@ export default class SimpleTable extends Simple {
|
|
|
1903
1905
|
* @param rightTable - The SimpleTable instance to be joined with this table.
|
|
1904
1906
|
* @param leftColumn - The name of the column in this (left) table containing the text to compare.
|
|
1905
1907
|
* @param rightColumn - The name of the column in the right table containing the text to compare.
|
|
1908
|
+
* @param threshold - The minimum similarity score (0–100) required for two rows to be joined. For `method: "ratio"`, a length-based pre-filter is automatically applied based on the threshold to improve performance without losing accuracy.
|
|
1906
1909
|
* @param options - An optional object with configuration options:
|
|
1907
1910
|
* @param options.method - The rapidfuzz similarity algorithm to use. Defaults to `"ratio"`.
|
|
1908
1911
|
* - `"ratio"`: Overall similarity (Levenshtein-based).
|
|
1909
1912
|
* - `"partial_ratio"`: Best partial/substring similarity.
|
|
1910
1913
|
* - `"token_sort_ratio"`: Similarity after sorting tokens (words), useful for reordered words.
|
|
1911
1914
|
* - `"token_set_ratio"`: Similarity based on sets of tokens, ignoring duplicates and word order.
|
|
1912
|
-
* @param options.threshold - The minimum similarity score (0–100) required for two rows to be joined. Defaults to `80`.
|
|
1913
1915
|
* @param options.similarityColumn - If provided, a column with this name is added to the result containing the similarity score (0–100). If omitted, the score is not included in the output.
|
|
1914
1916
|
* @param options.outputTable - If `true`, the results will be stored in a new table with a generated name. If a string, it will be used as the name for the new table. If `false` or omitted, the current table will be overwritten. Defaults to `false`.
|
|
1917
|
+
* @param options.preFilterPrefixLen - An optional prefix length. Only strings sharing the same first N characters are compared. Note that prefix filtering is lossy (e.g. "John" vs. "Phon" will not match despite high similarity).
|
|
1915
1918
|
* @returns A promise that resolves to a table instance containing the fuzzy-joined data (either the modified current table or a new table).
|
|
1916
1919
|
* @category Table Operations
|
|
1917
1920
|
*
|
|
1918
1921
|
* @example
|
|
1919
1922
|
* ```ts
|
|
1920
|
-
* // Fuzzy left join tableA with tableB on 'name' (left) and 'standardName' (right)
|
|
1921
|
-
*
|
|
1923
|
+
* // Fuzzy left join tableA with tableB on 'name' (left) and 'standardName' (right) with a threshold of 80
|
|
1924
|
+
* // A length-based pre-filter is automatically applied.
|
|
1925
|
+
* await tableA.fuzzyJoin(tableB, "name", "standardName", 80);
|
|
1926
|
+
* ```
|
|
1927
|
+
*
|
|
1928
|
+
* @example
|
|
1929
|
+
* ```ts
|
|
1930
|
+
* // Fuzzy join with a prefix-based pre-filter and a threshold of 80
|
|
1931
|
+
* await tableA.fuzzyJoin(tableB, "name", "standardName", 80, {
|
|
1932
|
+
* preFilterPrefixLen: 3, // Must share the same first 3 characters
|
|
1933
|
+
* });
|
|
1922
1934
|
* ```
|
|
1923
1935
|
*
|
|
1924
1936
|
* @example
|
|
1925
1937
|
* ```ts
|
|
1926
1938
|
* // Fuzzy join with a custom threshold and method, storing results in a new table
|
|
1927
|
-
* const tableC = await tableA.fuzzyJoin(tableB, "name", "standardName", {
|
|
1939
|
+
* const tableC = await tableA.fuzzyJoin(tableB, "name", "standardName", 90, {
|
|
1928
1940
|
* method: "token_sort_ratio",
|
|
1929
|
-
* threshold: 90,
|
|
1930
1941
|
* outputTable: "tableC",
|
|
1931
1942
|
* });
|
|
1932
1943
|
* ```
|
|
1933
1944
|
*
|
|
1934
1945
|
* @example
|
|
1935
1946
|
* ```ts
|
|
1936
|
-
* // Fuzzy join with a custom similarity column name
|
|
1937
|
-
* await tableA.fuzzyJoin(tableB, "name", "standardName", {
|
|
1947
|
+
* // Fuzzy join with a custom similarity column name and a threshold of 80
|
|
1948
|
+
* await tableA.fuzzyJoin(tableB, "name", "standardName", 80, {
|
|
1938
1949
|
* similarityColumn: "matchScore",
|
|
1939
1950
|
* });
|
|
1940
1951
|
* ```
|
|
1941
1952
|
*/
|
|
1942
|
-
async fuzzyJoin(rightTable, leftColumn, rightColumn, options = {}) {
|
|
1953
|
+
async fuzzyJoin(rightTable, leftColumn, rightColumn, threshold, options = {}) {
|
|
1943
1954
|
if (options.outputTable === true) {
|
|
1944
1955
|
options.outputTable = `table${this.sdb.tableIncrement}`;
|
|
1945
1956
|
this.sdb.tableIncrement += 1;
|
|
1946
1957
|
}
|
|
1947
|
-
return await fuzzyJoin(this, rightTable, leftColumn, rightColumn, options);
|
|
1958
|
+
return await fuzzyJoin(this, rightTable, leftColumn, rightColumn, threshold, options);
|
|
1948
1959
|
}
|
|
1949
1960
|
/**
|
|
1950
1961
|
* Normalizes string values in a column by detecting fuzzy duplicates and replacing them with a single canonical value.
|
|
@@ -1958,42 +1969,52 @@ export default class SimpleTable extends Simple {
|
|
|
1958
1969
|
*
|
|
1959
1970
|
* @param column - The name of the column containing the strings to normalize.
|
|
1960
1971
|
* @param newColumn - The name of the column to write the normalized values to. Use the same name as `column` to normalize in-place.
|
|
1972
|
+
* @param threshold - The minimum similarity score (0–100) for two strings to be considered duplicates. For `method: "ratio"`, a length-based pre-filter is automatically applied based on the threshold to improve performance without losing accuracy.
|
|
1961
1973
|
* @param options - An optional object with configuration options:
|
|
1962
1974
|
* @param options.method - The rapidfuzz similarity algorithm to use. Defaults to `"ratio"`.
|
|
1963
1975
|
* - `"ratio"`: Overall similarity.
|
|
1964
1976
|
* - `"partial_ratio"`: Best partial/substring similarity.
|
|
1965
1977
|
* - `"token_sort_ratio"`: Similarity after sorting tokens (words), useful for reordered words.
|
|
1966
1978
|
* - `"token_set_ratio"`: Similarity based on sets of tokens, ignoring duplicates and word order.
|
|
1967
|
-
* @param options.threshold - The minimum similarity score (0–100) for two strings to be considered duplicates. Defaults to `80`.
|
|
1968
1979
|
* @param options.keep - The strategy for choosing the canonical value within each cluster of similar strings. Defaults to `"mostCommon"`.
|
|
1969
1980
|
* - `"mostCommon"`: Keep the value that appears most frequently in the original column.
|
|
1970
1981
|
* - `"longestString"`: Keep the longest string in the cluster.
|
|
1971
1982
|
* - `"shortestString"`: Keep the shortest string in the cluster.
|
|
1972
1983
|
* - `"mostCentral"`: Keep the string with the highest total similarity score to all other cluster members (the most "central" string).
|
|
1973
1984
|
* - `"maxScore"`: Keep the string that participates in the single highest-scoring pairwise match within the cluster.
|
|
1985
|
+
* @param options.preFilterPrefixLen - An optional prefix length. Only strings sharing the same first N characters are compared. Note that prefix filtering is lossy (e.g. "John" vs. "Phon" will not match despite high similarity).
|
|
1974
1986
|
* @returns A promise that resolves when the column has been normalized.
|
|
1975
1987
|
* @category Updating Data
|
|
1976
1988
|
*
|
|
1977
1989
|
* @example
|
|
1978
1990
|
* ```ts
|
|
1979
|
-
* // Normalize 'city' into a new 'cityClean' column, keeping the most common string per cluster
|
|
1980
|
-
*
|
|
1991
|
+
* // Normalize 'city' into a new 'cityClean' column, keeping the most common string per cluster with a threshold of 80
|
|
1992
|
+
* // A length-based pre-filter is automatically applied.
|
|
1993
|
+
* await table.fuzzyClean("city", "cityClean", 80);
|
|
1981
1994
|
* ```
|
|
1982
1995
|
*
|
|
1983
1996
|
* @example
|
|
1984
1997
|
* ```ts
|
|
1985
|
-
* // Normalize
|
|
1986
|
-
* await table.fuzzyClean("
|
|
1998
|
+
* // Normalize with a prefix-based pre-filter and a threshold of 80
|
|
1999
|
+
* await table.fuzzyClean("city", "cityClean", 80, {
|
|
2000
|
+
* preFilterPrefixLen: 5, // Must share the same first 5 characters
|
|
2001
|
+
* });
|
|
1987
2002
|
* ```
|
|
1988
2003
|
*
|
|
1989
2004
|
* @example
|
|
1990
2005
|
* ```ts
|
|
1991
|
-
* // Normalize '
|
|
1992
|
-
* await table.fuzzyClean("
|
|
2006
|
+
* // Normalize 'companyName' into a new column using token_sort_ratio and a threshold of 90
|
|
2007
|
+
* await table.fuzzyClean("companyName", "companyNameClean", 90, { method: "token_sort_ratio" });
|
|
2008
|
+
* ```
|
|
2009
|
+
*
|
|
2010
|
+
* @example
|
|
2011
|
+
* ```ts
|
|
2012
|
+
* // Normalize 'category' in-place, keeping the longest string in each cluster and a threshold of 80
|
|
2013
|
+
* await table.fuzzyClean("category", "category", 80, { keep: "longestString" });
|
|
1993
2014
|
* ```
|
|
1994
2015
|
*/
|
|
1995
|
-
async fuzzyClean(column, newColumn, options = {}) {
|
|
1996
|
-
await fuzzyClean(this, column, newColumn, options);
|
|
2016
|
+
async fuzzyClean(column, newColumn, threshold, options = {}) {
|
|
2017
|
+
await fuzzyClean(this, column, newColumn, threshold, options);
|
|
1997
2018
|
}
|
|
1998
2019
|
/**
|
|
1999
2020
|
* Replaces specified strings in the selected columns.
|
|
@@ -3925,6 +3946,9 @@ export default class SimpleTable extends Simple {
|
|
|
3925
3946
|
* ```
|
|
3926
3947
|
*/
|
|
3927
3948
|
async getData(options = {}) {
|
|
3949
|
+
if (await hasGeometryColumn(this)) {
|
|
3950
|
+
throw new Error("Table contains geometry columns. Use getGeoData() instead.");
|
|
3951
|
+
}
|
|
3928
3952
|
const columns = options.columns
|
|
3929
3953
|
? (typeof options.columns === "string"
|
|
3930
3954
|
? [options.columns]
|
|
@@ -3989,8 +4013,9 @@ export default class SimpleTable extends Simple {
|
|
|
3989
4013
|
* ```
|
|
3990
4014
|
*/
|
|
3991
4015
|
async points(columnLat, columnLon, newColumn) {
|
|
3992
|
-
await queryDB(this,
|
|
3993
|
-
|
|
4016
|
+
await queryDB(this, (await this.getColumns()).includes(newColumn)
|
|
4017
|
+
? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Point2D("${columnLat}", "${columnLon}")`
|
|
4018
|
+
: `INSTALL spatial; LOAD spatial; ALTER TABLE "${this.name}" ADD COLUMN "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Point2D("${columnLat}", "${columnLon}")`, mergeOptions(this, {
|
|
3994
4019
|
table: this.name,
|
|
3995
4020
|
method: "points()",
|
|
3996
4021
|
parameters: { columnLat, columnLon, newColumn },
|
|
@@ -4406,7 +4431,9 @@ export default class SimpleTable extends Simple {
|
|
|
4406
4431
|
const column = typeof options.column === "string"
|
|
4407
4432
|
? options.column
|
|
4408
4433
|
: await findGeoColumn(this);
|
|
4409
|
-
await queryDB(this,
|
|
4434
|
+
await queryDB(this, (await this.getColumns()).includes(newColumn)
|
|
4435
|
+
? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Buffer("${column}", ${distance})`
|
|
4436
|
+
: `INSTALL spatial; LOAD spatial; ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Buffer("${column}", ${distance})`, mergeOptions(this, {
|
|
4410
4437
|
table: this.name,
|
|
4411
4438
|
method: "buffer()",
|
|
4412
4439
|
parameters: { column, newColumn, distance },
|
|
@@ -4492,7 +4519,9 @@ export default class SimpleTable extends Simple {
|
|
|
4492
4519
|
if (this.projections[column1] !== this.projections[column2]) {
|
|
4493
4520
|
throw new Error(`${column1} and ${column2} don't have the same projection.\n${column1}: ${this.projections[column1]}\n${column2}: ${this.projections[column2]}`);
|
|
4494
4521
|
}
|
|
4495
|
-
await queryDB(this,
|
|
4522
|
+
await queryDB(this, (await this.getColumns()).includes(newColumn)
|
|
4523
|
+
? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Intersection("${column1}", "${column2}")`
|
|
4524
|
+
: `INSTALL spatial; LOAD spatial; ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Intersection("${column1}", "${column2}")`, mergeOptions(this, {
|
|
4496
4525
|
table: this.name,
|
|
4497
4526
|
method: "intersection()",
|
|
4498
4527
|
parameters: { column1, column2, newColumn },
|
|
@@ -4617,7 +4646,9 @@ export default class SimpleTable extends Simple {
|
|
|
4617
4646
|
if (this.projections[column1] !== this.projections[column2]) {
|
|
4618
4647
|
throw new Error(`${column1} and ${column2} don't have the same projection.\n${column1}: ${this.projections[column1]}\n${column2}: ${this.projections[column2]}`);
|
|
4619
4648
|
}
|
|
4620
|
-
await queryDB(this,
|
|
4649
|
+
await queryDB(this, (await this.getColumns()).includes(newColumn)
|
|
4650
|
+
? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Union("${column1}", "${column2}")`
|
|
4651
|
+
: `INSTALL spatial; LOAD spatial; ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Union("${column1}", "${column2}")`, mergeOptions(this, {
|
|
4621
4652
|
table: this.name,
|
|
4622
4653
|
method: "union()",
|
|
4623
4654
|
parameters: { column1, column2, newColumn },
|
|
@@ -4708,7 +4739,9 @@ export default class SimpleTable extends Simple {
|
|
|
4708
4739
|
const column = typeof options.column === "string"
|
|
4709
4740
|
? options.column
|
|
4710
4741
|
: await findGeoColumn(this);
|
|
4711
|
-
await queryDB(this,
|
|
4742
|
+
await queryDB(this, (await this.getColumns()).includes(newColumn)
|
|
4743
|
+
? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Centroid("${column}")`
|
|
4744
|
+
: `INSTALL spatial; LOAD spatial; ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Centroid("${column}")`, mergeOptions(this, {
|
|
4712
4745
|
table: this.name,
|
|
4713
4746
|
method: "centroid()",
|
|
4714
4747
|
parameters: { column, newColumn },
|
|
@@ -4993,6 +5026,9 @@ export default class SimpleTable extends Simple {
|
|
|
4993
5026
|
* ```
|
|
4994
5027
|
*/
|
|
4995
5028
|
async writeData(file, options = {}) {
|
|
5029
|
+
if (await hasGeometryColumn(this)) {
|
|
5030
|
+
throw new Error("Table contains geometry columns. Use writeGeoData() instead.");
|
|
5031
|
+
}
|
|
4996
5032
|
createDirectory(file);
|
|
4997
5033
|
const extension = getExtension(file);
|
|
4998
5034
|
if (options.dataAsArrays) {
|
|
@@ -5007,12 +5043,12 @@ export default class SimpleTable extends Simple {
|
|
|
5007
5043
|
}
|
|
5008
5044
|
}
|
|
5009
5045
|
/**
|
|
5010
|
-
* Writes the table's geospatial data to a file in GeoJSON or
|
|
5046
|
+
* Writes the table's geospatial data to a file in GeoJSON, GeoParquet, or Shapefile format.
|
|
5011
5047
|
* If the specified path does not exist, it will be created.
|
|
5012
5048
|
*
|
|
5013
5049
|
* For GeoJSON files (`.geojson` or `.json`), if the projection is WGS84 or EPSG:4326 (`[latitude, longitude]` axis order), the coordinates will be flipped to follow the RFC7946 standard (`[longitude, latitude]` axis order) in the output.
|
|
5014
5050
|
*
|
|
5015
|
-
* @param file - The absolute path to the output file (e.g., `"./output.geojson"`, `"./output.geoparquet"`).
|
|
5051
|
+
* @param file - The absolute path to the output file (e.g., `"./output.geojson"`, `"./output.geoparquet"`, `"./shapefile-folder/output.shp"`).
|
|
5016
5052
|
* @param options - An optional object with configuration options:
|
|
5017
5053
|
* @param options.precision - For GeoJSON, the maximum number of figures after the decimal separator to write in coordinates. Defaults to `undefined` (full precision).
|
|
5018
5054
|
* @param options.compression - For GeoParquet, if `true`, the output will be ZSTD compressed. Defaults to `false`.
|
|
@@ -5036,6 +5072,12 @@ export default class SimpleTable extends Simple {
|
|
|
5036
5072
|
*
|
|
5037
5073
|
* @example
|
|
5038
5074
|
* ```ts
|
|
5075
|
+
* // Write geospatial data to a Shapefile with all relevant files in the same folder
|
|
5076
|
+
* await table.writeGeoData("./shapefile-folder/output.shp");
|
|
5077
|
+
* ```
|
|
5078
|
+
*
|
|
5079
|
+
* @example
|
|
5080
|
+
* ```ts
|
|
5039
5081
|
* // Write GeoJSON with specific precision and metadata
|
|
5040
5082
|
* await table.writeGeoData("./output_high_precision.geojson", {
|
|
5041
5083
|
* precision: 6,
|
|
@@ -5044,71 +5086,7 @@ export default class SimpleTable extends Simple {
|
|
|
5044
5086
|
* ```
|
|
5045
5087
|
*/
|
|
5046
5088
|
async writeGeoData(file, options = {}) {
|
|
5047
|
-
|
|
5048
|
-
const fileExtension = getExtension(file);
|
|
5049
|
-
if (fileExtension === "geojson" || fileExtension === "json") {
|
|
5050
|
-
let types;
|
|
5051
|
-
if (options.formatDates === true) {
|
|
5052
|
-
types = await this.getTypes();
|
|
5053
|
-
if (Object.values(types).includes("DATE") ||
|
|
5054
|
-
Object.values(types).includes("TIMESTAMP")) {
|
|
5055
|
-
await stringifyDates(this, types);
|
|
5056
|
-
}
|
|
5057
|
-
}
|
|
5058
|
-
if (typeof options.compression === "boolean") {
|
|
5059
|
-
throw new Error("The compression option is not supported for writing GeoJSON files.");
|
|
5060
|
-
}
|
|
5061
|
-
const geoColumn = await findGeoColumn(this);
|
|
5062
|
-
const flip = shouldFlipBeforeExport(this.projections[geoColumn]);
|
|
5063
|
-
if (flip) {
|
|
5064
|
-
await this.flipCoordinates(geoColumn);
|
|
5065
|
-
await queryDB(this, writeGeoDataQuery(this.name, file, fileExtension, options), mergeOptions(this, {
|
|
5066
|
-
table: this.name,
|
|
5067
|
-
method: "writeGeoData()",
|
|
5068
|
-
parameters: { file, options },
|
|
5069
|
-
}));
|
|
5070
|
-
await this.flipCoordinates(geoColumn);
|
|
5071
|
-
}
|
|
5072
|
-
else {
|
|
5073
|
-
await queryDB(this, writeGeoDataQuery(this.name, file, fileExtension, options), mergeOptions(this, {
|
|
5074
|
-
table: this.name,
|
|
5075
|
-
method: "writeGeoData()",
|
|
5076
|
-
parameters: { file, options },
|
|
5077
|
-
}));
|
|
5078
|
-
}
|
|
5079
|
-
if (options.metadata) {
|
|
5080
|
-
const fileData = JSON.parse(readFileSync(file, "utf-8"));
|
|
5081
|
-
fileData.metadata = options.metadata;
|
|
5082
|
-
writeFileSync(file, JSON.stringify(fileData));
|
|
5083
|
-
}
|
|
5084
|
-
if (options.rewind) {
|
|
5085
|
-
const fileData = JSON.parse(readFileSync(file, "utf-8"));
|
|
5086
|
-
const fileRewinded = rewind(fileData);
|
|
5087
|
-
writeFileSync(file, JSON.stringify(fileRewinded));
|
|
5088
|
-
}
|
|
5089
|
-
if (types && (Object.values(types).includes("DATE") ||
|
|
5090
|
-
Object.values(types).includes("TIMESTAMP"))) {
|
|
5091
|
-
await stringifyDatesInvert(this, types);
|
|
5092
|
-
}
|
|
5093
|
-
}
|
|
5094
|
-
else if (fileExtension === "geoparquet") {
|
|
5095
|
-
if (typeof options.precision === "number") {
|
|
5096
|
-
throw new Error("The precision option is not supported for writing PARQUET files. Use the .reducePrecision() method.");
|
|
5097
|
-
}
|
|
5098
|
-
if (typeof options.rewind === "boolean") {
|
|
5099
|
-
throw new Error("The rewind option is not supported for writing PARQUET files.");
|
|
5100
|
-
}
|
|
5101
|
-
await queryDB(this, `COPY "${this.name}" TO '${cleanPath(file)}' WITH (FORMAT PARQUET${options.compression === true ? ", COMPRESSION 'zstd'" : ""}, KV_METADATA {
|
|
5102
|
-
projections: '${JSON.stringify(this.projections)}'
|
|
5103
|
-
});`, mergeOptions(this, {
|
|
5104
|
-
table: this.name,
|
|
5105
|
-
method: "writeGeoData()",
|
|
5106
|
-
parameters: { file, options },
|
|
5107
|
-
}));
|
|
5108
|
-
}
|
|
5109
|
-
else {
|
|
5110
|
-
throw new Error(`Unknown extension ${fileExtension}`);
|
|
5111
|
-
}
|
|
5089
|
+
await writeGeoData(this, file, options);
|
|
5112
5090
|
}
|
|
5113
5091
|
/**
|
|
5114
5092
|
* Caches the results of computations in `./.sda-cache`.
|
|
@@ -3,7 +3,7 @@ export default async function findGeoColumn(SimpleTable) {
|
|
|
3
3
|
const types = await SimpleTable.getTypes();
|
|
4
4
|
const geometries = Object.values(types).filter((d) => d.toLowerCase() === "geometry");
|
|
5
5
|
if (geometries.length === 0) {
|
|
6
|
-
throw new Error("
|
|
6
|
+
throw new Error("Table contains no geometry columns.");
|
|
7
7
|
}
|
|
8
8
|
else if (geometries.length > 1) {
|
|
9
9
|
throw new Error("More than one column storing geometries. If the method allows to specify one, do it. Otherwise, use the selectColumns methods beforehand.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getProjection.d.ts","sourceRoot":"","sources":["../../src/helpers/getProjection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAC;AAGjD,wBAA8B,aAAa,CACzC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"getProjection.d.ts","sourceRoot":"","sources":["../../src/helpers/getProjection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAC;AAGjD,wBAA8B,aAAa,CACzC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,mBAiCb"}
|
|
@@ -15,6 +15,9 @@ export default async function getProjection(simpleDB, file) {
|
|
|
15
15
|
throw new Error("No queryResults");
|
|
16
16
|
}
|
|
17
17
|
const proj4 = queryResult[0].proj4;
|
|
18
|
+
if (proj4 === null) {
|
|
19
|
+
return "UNKNOWN";
|
|
20
|
+
}
|
|
18
21
|
if (typeof proj4 !== "string") {
|
|
19
22
|
throw new Error(`Expected proj4 to be a string, got ${typeof proj4}`);
|
|
20
23
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type SimpleTable from "../class/SimpleTable.js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns `true` if the table has one or more columns of type geometry.
|
|
4
|
+
*
|
|
5
|
+
* Uses `getTypes()` to inspect column types and checks for any column
|
|
6
|
+
* whose type normalizes to `"geometry"`.
|
|
7
|
+
*
|
|
8
|
+
* @param table - The SimpleTable instance to inspect.
|
|
9
|
+
* @returns `true` if at least one geometry column exists, `false` otherwise.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const hasGeo = await hasGeometryColumn(table);
|
|
14
|
+
* if (hasGeo) {
|
|
15
|
+
* console.log("This table contains geometry columns");
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export default function hasGeometryColumn(table: SimpleTable): Promise<boolean>;
|
|
20
|
+
//# sourceMappingURL=hasGeometryColumn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hasGeometryColumn.d.ts","sourceRoot":"","sources":["../../src/helpers/hasGeometryColumn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAA8B,iBAAiB,CAC7C,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,OAAO,CAAC,CAGlB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns `true` if the table has one or more columns of type geometry.
|
|
3
|
+
*
|
|
4
|
+
* Uses `getTypes()` to inspect column types and checks for any column
|
|
5
|
+
* whose type normalizes to `"geometry"`.
|
|
6
|
+
*
|
|
7
|
+
* @param table - The SimpleTable instance to inspect.
|
|
8
|
+
* @returns `true` if at least one geometry column exists, `false` otherwise.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const hasGeo = await hasGeometryColumn(table);
|
|
13
|
+
* if (hasGeo) {
|
|
14
|
+
* console.log("This table contains geometry columns");
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export default async function hasGeometryColumn(table) {
|
|
19
|
+
const types = await table.getTypes();
|
|
20
|
+
return Object.values(types).some((t) => t.toLowerCase() === "geometry");
|
|
21
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type SimpleTable from "../class/SimpleTable.js";
|
|
2
|
+
export default function writeGeoData(table: SimpleTable, file: string, options?: {
|
|
3
|
+
precision?: number;
|
|
4
|
+
compression?: boolean;
|
|
5
|
+
rewind?: boolean;
|
|
6
|
+
metadata?: unknown;
|
|
7
|
+
formatDates?: boolean;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=writeGeoData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writeGeoData.d.ts","sourceRoot":"","sources":["../../src/helpers/writeGeoData.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAEvD,wBAA8B,YAAY,CACxC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CAClB,GACL,OAAO,CAAC,IAAI,CAAC,CAqIf"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import createDirectory from "./createDirectory.js";
|
|
3
|
+
import getExtension from "./getExtension.js";
|
|
4
|
+
import findGeoColumn from "./findGeoColumn.js";
|
|
5
|
+
import hasGeometryColumn from "./hasGeometryColumn.js";
|
|
6
|
+
import shouldFlipBeforeExport from "./shouldFlipBeforeExport.js";
|
|
7
|
+
import queryDB from "./queryDB.js";
|
|
8
|
+
import writeGeoDataQuery from "../methods/writeGeoDataQuery.js";
|
|
9
|
+
import mergeOptions from "./mergeOptions.js";
|
|
10
|
+
import rewind from "./rewind.js";
|
|
11
|
+
import stringifyDates from "./stringifyDates.js";
|
|
12
|
+
import stringifyDatesInvert from "./stringifyDatesInvert.js";
|
|
13
|
+
import cleanPath from "./cleanPath.js";
|
|
14
|
+
export default async function writeGeoData(table, file, options = {}) {
|
|
15
|
+
createDirectory(file);
|
|
16
|
+
if (!(await hasGeometryColumn(table))) {
|
|
17
|
+
throw new Error("Table contains no geometry columns. Use writeData() instead.");
|
|
18
|
+
}
|
|
19
|
+
const fileExtension = getExtension(file);
|
|
20
|
+
if (fileExtension === "geojson" || fileExtension === "json") {
|
|
21
|
+
let types;
|
|
22
|
+
if (options.formatDates === true) {
|
|
23
|
+
types = await table.getTypes();
|
|
24
|
+
if (Object.values(types).includes("DATE") ||
|
|
25
|
+
Object.values(types).includes("TIMESTAMP")) {
|
|
26
|
+
await stringifyDates(table, types);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (typeof options.compression === "boolean") {
|
|
30
|
+
throw new Error("The compression option is not supported for writing GeoJSON files.");
|
|
31
|
+
}
|
|
32
|
+
const geoColumn = await findGeoColumn(table);
|
|
33
|
+
const flip = shouldFlipBeforeExport(table.projections[geoColumn]);
|
|
34
|
+
if (flip) {
|
|
35
|
+
await table.flipCoordinates(geoColumn);
|
|
36
|
+
await queryDB(table, writeGeoDataQuery(table.name, file, fileExtension, options), mergeOptions(table, {
|
|
37
|
+
table: table.name,
|
|
38
|
+
method: "writeGeoData()",
|
|
39
|
+
parameters: { file, options },
|
|
40
|
+
}));
|
|
41
|
+
await table.flipCoordinates(geoColumn);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
await queryDB(table, writeGeoDataQuery(table.name, file, fileExtension, options), mergeOptions(table, {
|
|
45
|
+
table: table.name,
|
|
46
|
+
method: "writeGeoData()",
|
|
47
|
+
parameters: { file, options },
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
if (options.metadata) {
|
|
51
|
+
const fileData = JSON.parse(readFileSync(file, "utf-8"));
|
|
52
|
+
fileData.metadata = options.metadata;
|
|
53
|
+
writeFileSync(file, JSON.stringify(fileData));
|
|
54
|
+
}
|
|
55
|
+
if (options.rewind) {
|
|
56
|
+
const fileData = JSON.parse(readFileSync(file, "utf-8"));
|
|
57
|
+
const fileRewinded = rewind(fileData);
|
|
58
|
+
writeFileSync(file, JSON.stringify(fileRewinded));
|
|
59
|
+
}
|
|
60
|
+
if (types && (Object.values(types).includes("DATE") ||
|
|
61
|
+
Object.values(types).includes("TIMESTAMP"))) {
|
|
62
|
+
await stringifyDatesInvert(table, types);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (fileExtension === "shp") {
|
|
66
|
+
if (typeof options.precision === "number" ||
|
|
67
|
+
typeof options.compression === "boolean" ||
|
|
68
|
+
typeof options.rewind === "boolean" ||
|
|
69
|
+
options.metadata ||
|
|
70
|
+
options.formatDates === true) {
|
|
71
|
+
throw new Error("The following options are not supported for writing SHAPEFILE files: precision, compression, rewind, metadata, and formatDates.");
|
|
72
|
+
}
|
|
73
|
+
const geoColumn = await findGeoColumn(table);
|
|
74
|
+
const flip = shouldFlipBeforeExport(table.projections[geoColumn]);
|
|
75
|
+
if (flip) {
|
|
76
|
+
await table.flipCoordinates(geoColumn);
|
|
77
|
+
await queryDB(table, writeGeoDataQuery(table.name, file, fileExtension, options), mergeOptions(table, {
|
|
78
|
+
table: table.name,
|
|
79
|
+
method: "writeGeoData()",
|
|
80
|
+
parameters: { file, options },
|
|
81
|
+
}));
|
|
82
|
+
await table.flipCoordinates(geoColumn);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
await queryDB(table, writeGeoDataQuery(table.name, file, fileExtension, options), mergeOptions(table, {
|
|
86
|
+
table: table.name,
|
|
87
|
+
method: "writeGeoData()",
|
|
88
|
+
parameters: { file, options },
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else if (fileExtension === "geoparquet") {
|
|
93
|
+
if (typeof options.precision === "number") {
|
|
94
|
+
throw new Error("The precision option is not supported for writing PARQUET files. Use the .reducePrecision() method.");
|
|
95
|
+
}
|
|
96
|
+
if (typeof options.rewind === "boolean") {
|
|
97
|
+
throw new Error("The rewind option is not supported for writing PARQUET files.");
|
|
98
|
+
}
|
|
99
|
+
await queryDB(table, `COPY "${table.name}" TO '${cleanPath(file)}' WITH (FORMAT PARQUET${options.compression === true ? ", COMPRESSION 'zstd'" : ""}, KV_METADATA {
|
|
100
|
+
projections: '${JSON.stringify(table.projections)}'
|
|
101
|
+
});`, mergeOptions(table, {
|
|
102
|
+
table: table.name,
|
|
103
|
+
method: "writeGeoData()",
|
|
104
|
+
parameters: { file, options },
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw new Error(`Unknown extension ${fileExtension}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type SimpleTable from "../class/SimpleTable.js";
|
|
2
|
-
export default function fuzzyClean(table: SimpleTable, column: string, newColumn: string, options?: {
|
|
2
|
+
export default function fuzzyClean(table: SimpleTable, column: string, newColumn: string, threshold: number, options?: {
|
|
3
3
|
method?: "ratio" | "partial_ratio" | "token_sort_ratio" | "token_set_ratio";
|
|
4
|
-
threshold?: number;
|
|
5
4
|
keep?: "mostCommon" | "longestString" | "shortestString" | "mostCentral" | "maxScore";
|
|
5
|
+
preFilterPrefixLen?: number;
|
|
6
6
|
}): Promise<void>;
|
|
7
7
|
//# sourceMappingURL=fuzzyClean.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fuzzyClean.d.ts","sourceRoot":"","sources":["../../src/methods/fuzzyClean.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAIvD,wBAA8B,UAAU,CACtC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACP,MAAM,CAAC,EACH,OAAO,GACP,eAAe,GACf,kBAAkB,GAClB,iBAAiB,CAAC;IACtB,
|
|
1
|
+
{"version":3,"file":"fuzzyClean.d.ts","sourceRoot":"","sources":["../../src/methods/fuzzyClean.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAIvD,wBAA8B,UAAU,CACtC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACP,MAAM,CAAC,EACH,OAAO,GACP,eAAe,GACf,kBAAkB,GAClB,iBAAiB,CAAC;IACtB,IAAI,CAAC,EACD,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,aAAa,GACb,UAAU,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACxB,GACL,OAAO,CAAC,IAAI,CAAC,CAmPf"}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import mergeOptions from "../helpers/mergeOptions.js";
|
|
2
2
|
import queryDB from "../helpers/queryDB.js";
|
|
3
|
-
export default async function fuzzyClean(table, column, newColumn, options = {}) {
|
|
3
|
+
export default async function fuzzyClean(table, column, newColumn, threshold, options = {}) {
|
|
4
4
|
const method = options.method ?? "ratio";
|
|
5
|
-
const threshold = options.threshold ?? 80;
|
|
6
5
|
const keep = options.keep ?? "mostCommon";
|
|
6
|
+
let onClause = `rapidfuzz_${method}(a.value, b.value) >= ${threshold}`;
|
|
7
|
+
if (method === "ratio") {
|
|
8
|
+
const maxDiffMultiplier = (200 - 2 * threshold) / (200 - threshold);
|
|
9
|
+
onClause +=
|
|
10
|
+
` AND ABS(LENGTH(a.value) - LENGTH(b.value)) <= ${maxDiffMultiplier} * GREATEST(LENGTH(a.value), LENGTH(b.value))`;
|
|
11
|
+
}
|
|
12
|
+
if (options.preFilterPrefixLen !== undefined) {
|
|
13
|
+
onClause +=
|
|
14
|
+
` AND SUBSTR(a.value, 1, ${options.preFilterPrefixLen}) = SUBSTR(b.value, 1, ${options.preFilterPrefixLen})`;
|
|
15
|
+
}
|
|
7
16
|
// Single round trip: compute fuzzy pairs and embed counts for both sides.
|
|
8
17
|
// Only values that appear in at least one pair above the threshold can be
|
|
9
18
|
// normalized — singletons need no processing at all.
|
|
@@ -22,16 +31,26 @@ export default async function fuzzyClean(table, column, newColumn, options = {})
|
|
|
22
31
|
rapidfuzz_${method}(a.value, b.value) AS score
|
|
23
32
|
FROM uniques a
|
|
24
33
|
JOIN uniques b
|
|
25
|
-
ON
|
|
34
|
+
ON ${onClause}
|
|
26
35
|
AND a.value < b.value`, mergeOptions(table, {
|
|
27
36
|
table: table.name,
|
|
28
37
|
method: "fuzzyClean()",
|
|
29
|
-
parameters: { column, newColumn, options },
|
|
38
|
+
parameters: { column, newColumn, threshold, options },
|
|
30
39
|
returnDataFrom: "query",
|
|
31
40
|
}));
|
|
32
41
|
const pairs = pairsData ?? [];
|
|
33
|
-
if (pairs.length === 0)
|
|
34
|
-
|
|
42
|
+
if (pairs.length === 0) {
|
|
43
|
+
if (newColumn !== column) {
|
|
44
|
+
await queryDB(table, `ALTER TABLE "${table.name}" ADD "${newColumn}" VARCHAR;
|
|
45
|
+
UPDATE "${table.name}"
|
|
46
|
+
SET "${newColumn}" = "${column}";`, mergeOptions(table, {
|
|
47
|
+
table: table.name,
|
|
48
|
+
method: "fuzzyClean()",
|
|
49
|
+
parameters: { column, newColumn, threshold, options },
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
35
54
|
// Build count map from the pairs — no separate query needed.
|
|
36
55
|
const countMap = new Map();
|
|
37
56
|
for (const { left_value, left_cnt, right_value, right_cnt } of pairs) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type SimpleTable from "../class/SimpleTable.js";
|
|
2
|
-
export default function fuzzyJoin(leftTable: SimpleTable, rightTable: SimpleTable, leftColumn: string, rightColumn: string, options?: {
|
|
2
|
+
export default function fuzzyJoin(leftTable: SimpleTable, rightTable: SimpleTable, leftColumn: string, rightColumn: string, threshold: number, options?: {
|
|
3
3
|
method?: "ratio" | "partial_ratio" | "token_sort_ratio" | "token_set_ratio";
|
|
4
|
-
threshold?: number;
|
|
5
4
|
similarityColumn?: string;
|
|
6
5
|
outputTable?: string | boolean;
|
|
6
|
+
preFilterPrefixLen?: number;
|
|
7
7
|
}): Promise<SimpleTable>;
|
|
8
8
|
//# sourceMappingURL=fuzzyJoin.d.ts.map
|