@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.
Files changed (47) hide show
  1. package/esm/class/SimpleTable.d.ts +52 -21
  2. package/esm/class/SimpleTable.d.ts.map +1 -1
  3. package/esm/class/SimpleTable.js +76 -98
  4. package/esm/helpers/findGeoColumn.js +1 -1
  5. package/esm/helpers/getProjection.d.ts.map +1 -1
  6. package/esm/helpers/getProjection.js +3 -0
  7. package/esm/helpers/hasGeometryColumn.d.ts +20 -0
  8. package/esm/helpers/hasGeometryColumn.d.ts.map +1 -0
  9. package/esm/helpers/hasGeometryColumn.js +21 -0
  10. package/esm/helpers/writeGeoData.d.ts +9 -0
  11. package/esm/helpers/writeGeoData.d.ts.map +1 -0
  12. package/esm/helpers/writeGeoData.js +110 -0
  13. package/esm/methods/fuzzyClean.d.ts +2 -2
  14. package/esm/methods/fuzzyClean.d.ts.map +1 -1
  15. package/esm/methods/fuzzyClean.js +25 -6
  16. package/esm/methods/fuzzyJoin.d.ts +2 -2
  17. package/esm/methods/fuzzyJoin.d.ts.map +1 -1
  18. package/esm/methods/fuzzyJoin.js +3 -3
  19. package/esm/methods/fuzzyJoinQuery.d.ts +1 -1
  20. package/esm/methods/fuzzyJoinQuery.d.ts.map +1 -1
  21. package/esm/methods/fuzzyJoinQuery.js +13 -3
  22. package/esm/methods/writeGeoDataQuery.d.ts.map +1 -1
  23. package/esm/methods/writeGeoDataQuery.js +4 -1
  24. package/package.json +1 -1
  25. package/script/class/SimpleTable.d.ts +52 -21
  26. package/script/class/SimpleTable.d.ts.map +1 -1
  27. package/script/class/SimpleTable.js +76 -98
  28. package/script/helpers/findGeoColumn.js +1 -1
  29. package/script/helpers/getProjection.d.ts.map +1 -1
  30. package/script/helpers/getProjection.js +3 -0
  31. package/script/helpers/hasGeometryColumn.d.ts +20 -0
  32. package/script/helpers/hasGeometryColumn.d.ts.map +1 -0
  33. package/script/helpers/hasGeometryColumn.js +24 -0
  34. package/script/helpers/writeGeoData.d.ts +9 -0
  35. package/script/helpers/writeGeoData.d.ts.map +1 -0
  36. package/script/helpers/writeGeoData.js +116 -0
  37. package/script/methods/fuzzyClean.d.ts +2 -2
  38. package/script/methods/fuzzyClean.d.ts.map +1 -1
  39. package/script/methods/fuzzyClean.js +25 -6
  40. package/script/methods/fuzzyJoin.d.ts +2 -2
  41. package/script/methods/fuzzyJoin.d.ts.map +1 -1
  42. package/script/methods/fuzzyJoin.js +3 -3
  43. package/script/methods/fuzzyJoinQuery.d.ts +1 -1
  44. package/script/methods/fuzzyJoinQuery.d.ts.map +1 -1
  45. package/script/methods/fuzzyJoinQuery.js +13 -3
  46. package/script/methods/writeGeoDataQuery.d.ts.map +1 -1
  47. package/script/methods/writeGeoDataQuery.js +4 -1
@@ -52,6 +52,7 @@ const normalizeQuery_js_1 = __importDefault(require("../methods/normalizeQuery.j
52
52
  const rollingQuery_js_1 = __importDefault(require("../methods/rollingQuery.js"));
53
53
  const distanceQuery_js_1 = __importDefault(require("../methods/distanceQuery.js"));
54
54
  const getGeoData_js_1 = __importDefault(require("../methods/getGeoData.js"));
55
+ const writeGeoData_js_1 = __importDefault(require("../helpers/writeGeoData.js"));
55
56
  const splitSpread_js_1 = __importDefault(require("../methods/splitSpread.js"));
56
57
  const node_fs_1 = require("node:fs");
57
58
  const stringToArray_js_1 = __importDefault(require("../helpers/stringToArray.js"));
@@ -59,24 +60,20 @@ const loadDataQuery_js_1 = __importDefault(require("../methods/loadDataQuery.js"
59
60
  const mergeOptions_js_1 = __importDefault(require("../helpers/mergeOptions.js"));
60
61
  const queryDB_js_1 = __importDefault(require("../helpers/queryDB.js"));
61
62
  const writeDataQuery_js_1 = __importDefault(require("../methods/writeDataQuery.js"));
62
- const writeGeoDataQuery_js_1 = __importDefault(require("../methods/writeGeoDataQuery.js"));
63
63
  const runQuery_js_1 = __importDefault(require("../helpers/runQuery.js"));
64
64
  const aggregateGeoQuery_js_1 = __importDefault(require("../methods/aggregateGeoQuery.js"));
65
65
  const summarize_js_1 = __importDefault(require("../methods/summarize.js"));
66
66
  const correlations_js_1 = __importDefault(require("../methods/correlations.js"));
67
67
  const linearRegressions_js_1 = __importDefault(require("../methods/linearRegressions.js"));
68
68
  const joinGeo_js_1 = __importDefault(require("../methods/joinGeo.js"));
69
- const shouldFlipBeforeExport_js_1 = __importDefault(require("../helpers/shouldFlipBeforeExport.js"));
70
69
  const getProjection_js_1 = __importDefault(require("../helpers/getProjection.js"));
71
70
  const cache_js_1 = __importDefault(require("../methods/cache.js"));
72
71
  const camelCase_js_1 = __importDefault(require("../helpers/camelCase.js"));
73
72
  const formatNumber_js_1 = __importDefault(require("../helpers/formatNumber.js"));
74
73
  const createDirectory_js_1 = __importDefault(require("../helpers/createDirectory.js"));
75
- const rewind_js_1 = __importDefault(require("../helpers/rewind.js"));
76
74
  const writeDataAsArrays_js_1 = __importDefault(require("../helpers/writeDataAsArrays.js"));
77
75
  const logData_js_1 = __importDefault(require("../helpers/logData.js"));
78
76
  const fill_js_1 = __importDefault(require("../methods/fill.js"));
79
- const node_fs_2 = require("node:fs");
80
77
  const loadArray_js_1 = __importDefault(require("../methods/loadArray.js"));
81
78
  const cleanPath_js_1 = __importDefault(require("../helpers/cleanPath.js"));
82
79
  const Simple_js_1 = __importDefault(require("./Simple.js"));
@@ -93,10 +90,9 @@ const capitalizeQuery_js_1 = __importDefault(require("../methods/capitalizeQuery
93
90
  const truncateQuery_js_1 = __importDefault(require("../methods/truncateQuery.js"));
94
91
  const padQuery_js_1 = __importDefault(require("../methods/padQuery.js"));
95
92
  const getProjectionParquet_js_1 = __importDefault(require("../helpers/getProjectionParquet.js"));
93
+ const hasGeometryColumn_js_1 = __importDefault(require("../helpers/hasGeometryColumn.js"));
96
94
  const unifyColumns_js_1 = __importDefault(require("../helpers/unifyColumns.js"));
97
95
  const accumulateQuery_js_1 = __importDefault(require("../helpers/accumulateQuery.js"));
98
- const stringifyDates_js_1 = __importDefault(require("../helpers/stringifyDates.js"));
99
- const stringifyDatesInvert_js_1 = __importDefault(require("../helpers/stringifyDatesInvert.js"));
100
96
  const unnestQuery_js_1 = __importDefault(require("../helpers/unnestQuery.js"));
101
97
  const nestQuery_js_1 = __importDefault(require("../helpers/nestQuery.js"));
102
98
  const concatenateRowQuery_js_1 = __importDefault(require("../helpers/concatenateRowQuery.js"));
@@ -427,7 +423,13 @@ class SimpleTable extends Simple_js_1.default {
427
423
  *
428
424
  * @example
429
425
  * ```ts
430
- * // Load geospatial data from a shapefile and reproject to WGS84
426
+ * // Load geospatial data from a shapefile (with relevant files in the same folder) and reproject to WGS84
427
+ * await table.loadGeoData("./some-data/some-data.shp", { toWGS84: true });
428
+ * ```
429
+ *
430
+ * @example
431
+ * ```ts
432
+ * // Load geospatial data from a zipped shapefile and reproject to WGS84
431
433
  * await table.loadGeoData("./some-data.shp.zip", { toWGS84: true });
432
434
  * ```
433
435
  */
@@ -1908,48 +1910,57 @@ class SimpleTable extends Simple_js_1.default {
1908
1910
  * @param rightTable - The SimpleTable instance to be joined with this table.
1909
1911
  * @param leftColumn - The name of the column in this (left) table containing the text to compare.
1910
1912
  * @param rightColumn - The name of the column in the right table containing the text to compare.
1913
+ * @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.
1911
1914
  * @param options - An optional object with configuration options:
1912
1915
  * @param options.method - The rapidfuzz similarity algorithm to use. Defaults to `"ratio"`.
1913
1916
  * - `"ratio"`: Overall similarity (Levenshtein-based).
1914
1917
  * - `"partial_ratio"`: Best partial/substring similarity.
1915
1918
  * - `"token_sort_ratio"`: Similarity after sorting tokens (words), useful for reordered words.
1916
1919
  * - `"token_set_ratio"`: Similarity based on sets of tokens, ignoring duplicates and word order.
1917
- * @param options.threshold - The minimum similarity score (0–100) required for two rows to be joined. Defaults to `80`.
1918
1920
  * @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.
1919
1921
  * @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`.
1922
+ * @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).
1920
1923
  * @returns A promise that resolves to a table instance containing the fuzzy-joined data (either the modified current table or a new table).
1921
1924
  * @category Table Operations
1922
1925
  *
1923
1926
  * @example
1924
1927
  * ```ts
1925
- * // Fuzzy left join tableA with tableB on 'name' (left) and 'standardName' (right) (ratio >= 80)
1926
- * await tableA.fuzzyJoin(tableB, "name", "standardName");
1928
+ * // Fuzzy left join tableA with tableB on 'name' (left) and 'standardName' (right) with a threshold of 80
1929
+ * // A length-based pre-filter is automatically applied.
1930
+ * await tableA.fuzzyJoin(tableB, "name", "standardName", 80);
1931
+ * ```
1932
+ *
1933
+ * @example
1934
+ * ```ts
1935
+ * // Fuzzy join with a prefix-based pre-filter and a threshold of 80
1936
+ * await tableA.fuzzyJoin(tableB, "name", "standardName", 80, {
1937
+ * preFilterPrefixLen: 3, // Must share the same first 3 characters
1938
+ * });
1927
1939
  * ```
1928
1940
  *
1929
1941
  * @example
1930
1942
  * ```ts
1931
1943
  * // Fuzzy join with a custom threshold and method, storing results in a new table
1932
- * const tableC = await tableA.fuzzyJoin(tableB, "name", "standardName", {
1944
+ * const tableC = await tableA.fuzzyJoin(tableB, "name", "standardName", 90, {
1933
1945
  * method: "token_sort_ratio",
1934
- * threshold: 90,
1935
1946
  * outputTable: "tableC",
1936
1947
  * });
1937
1948
  * ```
1938
1949
  *
1939
1950
  * @example
1940
1951
  * ```ts
1941
- * // Fuzzy join with a custom similarity column name
1942
- * await tableA.fuzzyJoin(tableB, "name", "standardName", {
1952
+ * // Fuzzy join with a custom similarity column name and a threshold of 80
1953
+ * await tableA.fuzzyJoin(tableB, "name", "standardName", 80, {
1943
1954
  * similarityColumn: "matchScore",
1944
1955
  * });
1945
1956
  * ```
1946
1957
  */
1947
- async fuzzyJoin(rightTable, leftColumn, rightColumn, options = {}) {
1958
+ async fuzzyJoin(rightTable, leftColumn, rightColumn, threshold, options = {}) {
1948
1959
  if (options.outputTable === true) {
1949
1960
  options.outputTable = `table${this.sdb.tableIncrement}`;
1950
1961
  this.sdb.tableIncrement += 1;
1951
1962
  }
1952
- return await (0, fuzzyJoin_js_1.default)(this, rightTable, leftColumn, rightColumn, options);
1963
+ return await (0, fuzzyJoin_js_1.default)(this, rightTable, leftColumn, rightColumn, threshold, options);
1953
1964
  }
1954
1965
  /**
1955
1966
  * Normalizes string values in a column by detecting fuzzy duplicates and replacing them with a single canonical value.
@@ -1963,42 +1974,52 @@ class SimpleTable extends Simple_js_1.default {
1963
1974
  *
1964
1975
  * @param column - The name of the column containing the strings to normalize.
1965
1976
  * @param newColumn - The name of the column to write the normalized values to. Use the same name as `column` to normalize in-place.
1977
+ * @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.
1966
1978
  * @param options - An optional object with configuration options:
1967
1979
  * @param options.method - The rapidfuzz similarity algorithm to use. Defaults to `"ratio"`.
1968
1980
  * - `"ratio"`: Overall similarity.
1969
1981
  * - `"partial_ratio"`: Best partial/substring similarity.
1970
1982
  * - `"token_sort_ratio"`: Similarity after sorting tokens (words), useful for reordered words.
1971
1983
  * - `"token_set_ratio"`: Similarity based on sets of tokens, ignoring duplicates and word order.
1972
- * @param options.threshold - The minimum similarity score (0–100) for two strings to be considered duplicates. Defaults to `80`.
1973
1984
  * @param options.keep - The strategy for choosing the canonical value within each cluster of similar strings. Defaults to `"mostCommon"`.
1974
1985
  * - `"mostCommon"`: Keep the value that appears most frequently in the original column.
1975
1986
  * - `"longestString"`: Keep the longest string in the cluster.
1976
1987
  * - `"shortestString"`: Keep the shortest string in the cluster.
1977
1988
  * - `"mostCentral"`: Keep the string with the highest total similarity score to all other cluster members (the most "central" string).
1978
1989
  * - `"maxScore"`: Keep the string that participates in the single highest-scoring pairwise match within the cluster.
1990
+ * @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).
1979
1991
  * @returns A promise that resolves when the column has been normalized.
1980
1992
  * @category Updating Data
1981
1993
  *
1982
1994
  * @example
1983
1995
  * ```ts
1984
- * // Normalize 'city' into a new 'cityClean' column, keeping the most common string per cluster
1985
- * await table.fuzzyClean("city", "cityClean");
1996
+ * // Normalize 'city' into a new 'cityClean' column, keeping the most common string per cluster with a threshold of 80
1997
+ * // A length-based pre-filter is automatically applied.
1998
+ * await table.fuzzyClean("city", "cityClean", 80);
1986
1999
  * ```
1987
2000
  *
1988
2001
  * @example
1989
2002
  * ```ts
1990
- * // Normalize 'companyName' into a new column using token_sort_ratio and a stricter threshold
1991
- * await table.fuzzyClean("companyName", "companyNameClean", { method: "token_sort_ratio", threshold: 90 });
2003
+ * // Normalize with a prefix-based pre-filter and a threshold of 80
2004
+ * await table.fuzzyClean("city", "cityClean", 80, {
2005
+ * preFilterPrefixLen: 5, // Must share the same first 5 characters
2006
+ * });
1992
2007
  * ```
1993
2008
  *
1994
2009
  * @example
1995
2010
  * ```ts
1996
- * // Normalize 'category' in-place, keeping the longest string in each cluster
1997
- * await table.fuzzyClean("category", "category", { keep: "longestString" });
2011
+ * // Normalize 'companyName' into a new column using token_sort_ratio and a threshold of 90
2012
+ * await table.fuzzyClean("companyName", "companyNameClean", 90, { method: "token_sort_ratio" });
2013
+ * ```
2014
+ *
2015
+ * @example
2016
+ * ```ts
2017
+ * // Normalize 'category' in-place, keeping the longest string in each cluster and a threshold of 80
2018
+ * await table.fuzzyClean("category", "category", 80, { keep: "longestString" });
1998
2019
  * ```
1999
2020
  */
2000
- async fuzzyClean(column, newColumn, options = {}) {
2001
- await (0, fuzzyClean_js_1.default)(this, column, newColumn, options);
2021
+ async fuzzyClean(column, newColumn, threshold, options = {}) {
2022
+ await (0, fuzzyClean_js_1.default)(this, column, newColumn, threshold, options);
2002
2023
  }
2003
2024
  /**
2004
2025
  * Replaces specified strings in the selected columns.
@@ -3930,6 +3951,9 @@ class SimpleTable extends Simple_js_1.default {
3930
3951
  * ```
3931
3952
  */
3932
3953
  async getData(options = {}) {
3954
+ if (await (0, hasGeometryColumn_js_1.default)(this)) {
3955
+ throw new Error("Table contains geometry columns. Use getGeoData() instead.");
3956
+ }
3933
3957
  const columns = options.columns
3934
3958
  ? (typeof options.columns === "string"
3935
3959
  ? [options.columns]
@@ -3994,8 +4018,9 @@ class SimpleTable extends Simple_js_1.default {
3994
4018
  * ```
3995
4019
  */
3996
4020
  async points(columnLat, columnLon, newColumn) {
3997
- await (0, queryDB_js_1.default)(this, `INSTALL spatial; LOAD spatial;
3998
- ALTER TABLE "${this.name}" ADD COLUMN "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Point2D("${columnLat}", "${columnLon}")`, (0, mergeOptions_js_1.default)(this, {
4021
+ await (0, queryDB_js_1.default)(this, (await this.getColumns()).includes(newColumn)
4022
+ ? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Point2D("${columnLat}", "${columnLon}")`
4023
+ : `INSTALL spatial; LOAD spatial; ALTER TABLE "${this.name}" ADD COLUMN "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Point2D("${columnLat}", "${columnLon}")`, (0, mergeOptions_js_1.default)(this, {
3999
4024
  table: this.name,
4000
4025
  method: "points()",
4001
4026
  parameters: { columnLat, columnLon, newColumn },
@@ -4411,7 +4436,9 @@ class SimpleTable extends Simple_js_1.default {
4411
4436
  const column = typeof options.column === "string"
4412
4437
  ? options.column
4413
4438
  : await (0, findGeoColumn_js_1.default)(this);
4414
- await (0, queryDB_js_1.default)(this, `ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Buffer("${column}", ${distance});`, (0, mergeOptions_js_1.default)(this, {
4439
+ await (0, queryDB_js_1.default)(this, (await this.getColumns()).includes(newColumn)
4440
+ ? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Buffer("${column}", ${distance})`
4441
+ : `INSTALL spatial; LOAD spatial; ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Buffer("${column}", ${distance})`, (0, mergeOptions_js_1.default)(this, {
4415
4442
  table: this.name,
4416
4443
  method: "buffer()",
4417
4444
  parameters: { column, newColumn, distance },
@@ -4497,7 +4524,9 @@ class SimpleTable extends Simple_js_1.default {
4497
4524
  if (this.projections[column1] !== this.projections[column2]) {
4498
4525
  throw new Error(`${column1} and ${column2} don't have the same projection.\n${column1}: ${this.projections[column1]}\n${column2}: ${this.projections[column2]}`);
4499
4526
  }
4500
- await (0, queryDB_js_1.default)(this, `ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Intersection("${column1}", "${column2}")`, (0, mergeOptions_js_1.default)(this, {
4527
+ await (0, queryDB_js_1.default)(this, (await this.getColumns()).includes(newColumn)
4528
+ ? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Intersection("${column1}", "${column2}")`
4529
+ : `INSTALL spatial; LOAD spatial; ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Intersection("${column1}", "${column2}")`, (0, mergeOptions_js_1.default)(this, {
4501
4530
  table: this.name,
4502
4531
  method: "intersection()",
4503
4532
  parameters: { column1, column2, newColumn },
@@ -4622,7 +4651,9 @@ class SimpleTable extends Simple_js_1.default {
4622
4651
  if (this.projections[column1] !== this.projections[column2]) {
4623
4652
  throw new Error(`${column1} and ${column2} don't have the same projection.\n${column1}: ${this.projections[column1]}\n${column2}: ${this.projections[column2]}`);
4624
4653
  }
4625
- await (0, queryDB_js_1.default)(this, `ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Union("${column1}", "${column2}")`, (0, mergeOptions_js_1.default)(this, {
4654
+ await (0, queryDB_js_1.default)(this, (await this.getColumns()).includes(newColumn)
4655
+ ? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Union("${column1}", "${column2}")`
4656
+ : `INSTALL spatial; LOAD spatial; ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Union("${column1}", "${column2}")`, (0, mergeOptions_js_1.default)(this, {
4626
4657
  table: this.name,
4627
4658
  method: "union()",
4628
4659
  parameters: { column1, column2, newColumn },
@@ -4713,7 +4744,9 @@ class SimpleTable extends Simple_js_1.default {
4713
4744
  const column = typeof options.column === "string"
4714
4745
  ? options.column
4715
4746
  : await (0, findGeoColumn_js_1.default)(this);
4716
- await (0, queryDB_js_1.default)(this, `ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Centroid("${column}");`, (0, mergeOptions_js_1.default)(this, {
4747
+ await (0, queryDB_js_1.default)(this, (await this.getColumns()).includes(newColumn)
4748
+ ? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Centroid("${column}")`
4749
+ : `INSTALL spatial; LOAD spatial; ALTER TABLE "${this.name}" ADD "${newColumn}" GEOMETRY; UPDATE "${this.name}" SET "${newColumn}" = ST_Centroid("${column}")`, (0, mergeOptions_js_1.default)(this, {
4717
4750
  table: this.name,
4718
4751
  method: "centroid()",
4719
4752
  parameters: { column, newColumn },
@@ -4998,6 +5031,9 @@ class SimpleTable extends Simple_js_1.default {
4998
5031
  * ```
4999
5032
  */
5000
5033
  async writeData(file, options = {}) {
5034
+ if (await (0, hasGeometryColumn_js_1.default)(this)) {
5035
+ throw new Error("Table contains geometry columns. Use writeGeoData() instead.");
5036
+ }
5001
5037
  (0, createDirectory_js_1.default)(file);
5002
5038
  const extension = (0, getExtension_js_1.default)(file);
5003
5039
  if (options.dataAsArrays) {
@@ -5012,12 +5048,12 @@ class SimpleTable extends Simple_js_1.default {
5012
5048
  }
5013
5049
  }
5014
5050
  /**
5015
- * Writes the table's geospatial data to a file in GeoJSON or GeoParquet format.
5051
+ * Writes the table's geospatial data to a file in GeoJSON, GeoParquet, or Shapefile format.
5016
5052
  * If the specified path does not exist, it will be created.
5017
5053
  *
5018
5054
  * 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.
5019
5055
  *
5020
- * @param file - The absolute path to the output file (e.g., `"./output.geojson"`, `"./output.geoparquet"`).
5056
+ * @param file - The absolute path to the output file (e.g., `"./output.geojson"`, `"./output.geoparquet"`, `"./shapefile-folder/output.shp"`).
5021
5057
  * @param options - An optional object with configuration options:
5022
5058
  * @param options.precision - For GeoJSON, the maximum number of figures after the decimal separator to write in coordinates. Defaults to `undefined` (full precision).
5023
5059
  * @param options.compression - For GeoParquet, if `true`, the output will be ZSTD compressed. Defaults to `false`.
@@ -5041,6 +5077,12 @@ class SimpleTable extends Simple_js_1.default {
5041
5077
  *
5042
5078
  * @example
5043
5079
  * ```ts
5080
+ * // Write geospatial data to a Shapefile with all relevant files in the same folder
5081
+ * await table.writeGeoData("./shapefile-folder/output.shp");
5082
+ * ```
5083
+ *
5084
+ * @example
5085
+ * ```ts
5044
5086
  * // Write GeoJSON with specific precision and metadata
5045
5087
  * await table.writeGeoData("./output_high_precision.geojson", {
5046
5088
  * precision: 6,
@@ -5049,71 +5091,7 @@ class SimpleTable extends Simple_js_1.default {
5049
5091
  * ```
5050
5092
  */
5051
5093
  async writeGeoData(file, options = {}) {
5052
- (0, createDirectory_js_1.default)(file);
5053
- const fileExtension = (0, getExtension_js_1.default)(file);
5054
- if (fileExtension === "geojson" || fileExtension === "json") {
5055
- let types;
5056
- if (options.formatDates === true) {
5057
- types = await this.getTypes();
5058
- if (Object.values(types).includes("DATE") ||
5059
- Object.values(types).includes("TIMESTAMP")) {
5060
- await (0, stringifyDates_js_1.default)(this, types);
5061
- }
5062
- }
5063
- if (typeof options.compression === "boolean") {
5064
- throw new Error("The compression option is not supported for writing GeoJSON files.");
5065
- }
5066
- const geoColumn = await (0, findGeoColumn_js_1.default)(this);
5067
- const flip = (0, shouldFlipBeforeExport_js_1.default)(this.projections[geoColumn]);
5068
- if (flip) {
5069
- await this.flipCoordinates(geoColumn);
5070
- await (0, queryDB_js_1.default)(this, (0, writeGeoDataQuery_js_1.default)(this.name, file, fileExtension, options), (0, mergeOptions_js_1.default)(this, {
5071
- table: this.name,
5072
- method: "writeGeoData()",
5073
- parameters: { file, options },
5074
- }));
5075
- await this.flipCoordinates(geoColumn);
5076
- }
5077
- else {
5078
- await (0, queryDB_js_1.default)(this, (0, writeGeoDataQuery_js_1.default)(this.name, file, fileExtension, options), (0, mergeOptions_js_1.default)(this, {
5079
- table: this.name,
5080
- method: "writeGeoData()",
5081
- parameters: { file, options },
5082
- }));
5083
- }
5084
- if (options.metadata) {
5085
- const fileData = JSON.parse((0, node_fs_2.readFileSync)(file, "utf-8"));
5086
- fileData.metadata = options.metadata;
5087
- (0, node_fs_2.writeFileSync)(file, JSON.stringify(fileData));
5088
- }
5089
- if (options.rewind) {
5090
- const fileData = JSON.parse((0, node_fs_2.readFileSync)(file, "utf-8"));
5091
- const fileRewinded = (0, rewind_js_1.default)(fileData);
5092
- (0, node_fs_2.writeFileSync)(file, JSON.stringify(fileRewinded));
5093
- }
5094
- if (types && (Object.values(types).includes("DATE") ||
5095
- Object.values(types).includes("TIMESTAMP"))) {
5096
- await (0, stringifyDatesInvert_js_1.default)(this, types);
5097
- }
5098
- }
5099
- else if (fileExtension === "geoparquet") {
5100
- if (typeof options.precision === "number") {
5101
- throw new Error("The precision option is not supported for writing PARQUET files. Use the .reducePrecision() method.");
5102
- }
5103
- if (typeof options.rewind === "boolean") {
5104
- throw new Error("The rewind option is not supported for writing PARQUET files.");
5105
- }
5106
- await (0, queryDB_js_1.default)(this, `COPY "${this.name}" TO '${(0, cleanPath_js_1.default)(file)}' WITH (FORMAT PARQUET${options.compression === true ? ", COMPRESSION 'zstd'" : ""}, KV_METADATA {
5107
- projections: '${JSON.stringify(this.projections)}'
5108
- });`, (0, mergeOptions_js_1.default)(this, {
5109
- table: this.name,
5110
- method: "writeGeoData()",
5111
- parameters: { file, options },
5112
- }));
5113
- }
5114
- else {
5115
- throw new Error(`Unknown extension ${fileExtension}`);
5116
- }
5094
+ await (0, writeGeoData_js_1.default)(this, file, options);
5117
5095
  }
5118
5096
  /**
5119
5097
  * Caches the results of computations in `./.sda-cache`.
@@ -6,7 +6,7 @@ async function findGeoColumn(SimpleTable) {
6
6
  const types = await SimpleTable.getTypes();
7
7
  const geometries = Object.values(types).filter((d) => d.toLowerCase() === "geometry");
8
8
  if (geometries.length === 0) {
9
- throw new Error("No column storing geometries");
9
+ throw new Error("Table contains no geometry columns.");
10
10
  }
11
11
  else if (geometries.length > 1) {
12
12
  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,mBA8Bb"}
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"}
@@ -21,6 +21,9 @@ async function getProjection(simpleDB, file) {
21
21
  throw new Error("No queryResults");
22
22
  }
23
23
  const proj4 = queryResult[0].proj4;
24
+ if (proj4 === null) {
25
+ return "UNKNOWN";
26
+ }
24
27
  if (typeof proj4 !== "string") {
25
28
  throw new Error(`Expected proj4 to be a string, got ${typeof proj4}`);
26
29
  }
@@ -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,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = hasGeometryColumn;
4
+ /**
5
+ * Returns `true` if the table has one or more columns of type geometry.
6
+ *
7
+ * Uses `getTypes()` to inspect column types and checks for any column
8
+ * whose type normalizes to `"geometry"`.
9
+ *
10
+ * @param table - The SimpleTable instance to inspect.
11
+ * @returns `true` if at least one geometry column exists, `false` otherwise.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const hasGeo = await hasGeometryColumn(table);
16
+ * if (hasGeo) {
17
+ * console.log("This table contains geometry columns");
18
+ * }
19
+ * ```
20
+ */
21
+ async function hasGeometryColumn(table) {
22
+ const types = await table.getTypes();
23
+ return Object.values(types).some((t) => t.toLowerCase() === "geometry");
24
+ }
@@ -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,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = writeGeoData;
7
+ const node_fs_1 = require("node:fs");
8
+ const createDirectory_js_1 = __importDefault(require("./createDirectory.js"));
9
+ const getExtension_js_1 = __importDefault(require("./getExtension.js"));
10
+ const findGeoColumn_js_1 = __importDefault(require("./findGeoColumn.js"));
11
+ const hasGeometryColumn_js_1 = __importDefault(require("./hasGeometryColumn.js"));
12
+ const shouldFlipBeforeExport_js_1 = __importDefault(require("./shouldFlipBeforeExport.js"));
13
+ const queryDB_js_1 = __importDefault(require("./queryDB.js"));
14
+ const writeGeoDataQuery_js_1 = __importDefault(require("../methods/writeGeoDataQuery.js"));
15
+ const mergeOptions_js_1 = __importDefault(require("./mergeOptions.js"));
16
+ const rewind_js_1 = __importDefault(require("./rewind.js"));
17
+ const stringifyDates_js_1 = __importDefault(require("./stringifyDates.js"));
18
+ const stringifyDatesInvert_js_1 = __importDefault(require("./stringifyDatesInvert.js"));
19
+ const cleanPath_js_1 = __importDefault(require("./cleanPath.js"));
20
+ async function writeGeoData(table, file, options = {}) {
21
+ (0, createDirectory_js_1.default)(file);
22
+ if (!(await (0, hasGeometryColumn_js_1.default)(table))) {
23
+ throw new Error("Table contains no geometry columns. Use writeData() instead.");
24
+ }
25
+ const fileExtension = (0, getExtension_js_1.default)(file);
26
+ if (fileExtension === "geojson" || fileExtension === "json") {
27
+ let types;
28
+ if (options.formatDates === true) {
29
+ types = await table.getTypes();
30
+ if (Object.values(types).includes("DATE") ||
31
+ Object.values(types).includes("TIMESTAMP")) {
32
+ await (0, stringifyDates_js_1.default)(table, types);
33
+ }
34
+ }
35
+ if (typeof options.compression === "boolean") {
36
+ throw new Error("The compression option is not supported for writing GeoJSON files.");
37
+ }
38
+ const geoColumn = await (0, findGeoColumn_js_1.default)(table);
39
+ const flip = (0, shouldFlipBeforeExport_js_1.default)(table.projections[geoColumn]);
40
+ if (flip) {
41
+ await table.flipCoordinates(geoColumn);
42
+ await (0, queryDB_js_1.default)(table, (0, writeGeoDataQuery_js_1.default)(table.name, file, fileExtension, options), (0, mergeOptions_js_1.default)(table, {
43
+ table: table.name,
44
+ method: "writeGeoData()",
45
+ parameters: { file, options },
46
+ }));
47
+ await table.flipCoordinates(geoColumn);
48
+ }
49
+ else {
50
+ await (0, queryDB_js_1.default)(table, (0, writeGeoDataQuery_js_1.default)(table.name, file, fileExtension, options), (0, mergeOptions_js_1.default)(table, {
51
+ table: table.name,
52
+ method: "writeGeoData()",
53
+ parameters: { file, options },
54
+ }));
55
+ }
56
+ if (options.metadata) {
57
+ const fileData = JSON.parse((0, node_fs_1.readFileSync)(file, "utf-8"));
58
+ fileData.metadata = options.metadata;
59
+ (0, node_fs_1.writeFileSync)(file, JSON.stringify(fileData));
60
+ }
61
+ if (options.rewind) {
62
+ const fileData = JSON.parse((0, node_fs_1.readFileSync)(file, "utf-8"));
63
+ const fileRewinded = (0, rewind_js_1.default)(fileData);
64
+ (0, node_fs_1.writeFileSync)(file, JSON.stringify(fileRewinded));
65
+ }
66
+ if (types && (Object.values(types).includes("DATE") ||
67
+ Object.values(types).includes("TIMESTAMP"))) {
68
+ await (0, stringifyDatesInvert_js_1.default)(table, types);
69
+ }
70
+ }
71
+ else if (fileExtension === "shp") {
72
+ if (typeof options.precision === "number" ||
73
+ typeof options.compression === "boolean" ||
74
+ typeof options.rewind === "boolean" ||
75
+ options.metadata ||
76
+ options.formatDates === true) {
77
+ throw new Error("The following options are not supported for writing SHAPEFILE files: precision, compression, rewind, metadata, and formatDates.");
78
+ }
79
+ const geoColumn = await (0, findGeoColumn_js_1.default)(table);
80
+ const flip = (0, shouldFlipBeforeExport_js_1.default)(table.projections[geoColumn]);
81
+ if (flip) {
82
+ await table.flipCoordinates(geoColumn);
83
+ await (0, queryDB_js_1.default)(table, (0, writeGeoDataQuery_js_1.default)(table.name, file, fileExtension, options), (0, mergeOptions_js_1.default)(table, {
84
+ table: table.name,
85
+ method: "writeGeoData()",
86
+ parameters: { file, options },
87
+ }));
88
+ await table.flipCoordinates(geoColumn);
89
+ }
90
+ else {
91
+ await (0, queryDB_js_1.default)(table, (0, writeGeoDataQuery_js_1.default)(table.name, file, fileExtension, options), (0, mergeOptions_js_1.default)(table, {
92
+ table: table.name,
93
+ method: "writeGeoData()",
94
+ parameters: { file, options },
95
+ }));
96
+ }
97
+ }
98
+ else if (fileExtension === "geoparquet") {
99
+ if (typeof options.precision === "number") {
100
+ throw new Error("The precision option is not supported for writing PARQUET files. Use the .reducePrecision() method.");
101
+ }
102
+ if (typeof options.rewind === "boolean") {
103
+ throw new Error("The rewind option is not supported for writing PARQUET files.");
104
+ }
105
+ await (0, queryDB_js_1.default)(table, `COPY "${table.name}" TO '${(0, cleanPath_js_1.default)(file)}' WITH (FORMAT PARQUET${options.compression === true ? ", COMPRESSION 'zstd'" : ""}, KV_METADATA {
106
+ projections: '${JSON.stringify(table.projections)}'
107
+ });`, (0, mergeOptions_js_1.default)(table, {
108
+ table: table.name,
109
+ method: "writeGeoData()",
110
+ parameters: { file, options },
111
+ }));
112
+ }
113
+ else {
114
+ throw new Error(`Unknown extension ${fileExtension}`);
115
+ }
116
+ }
@@ -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,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EACD,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,aAAa,GACb,UAAU,CAAC;CACX,GACL,OAAO,CAAC,IAAI,CAAC,CAyNf"}
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"}