@nshiab/simple-data-analysis-core 0.0.20 → 0.0.22

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 (53) hide show
  1. package/esm/class/SimpleTable.d.ts +76 -21
  2. package/esm/class/SimpleTable.d.ts.map +1 -1
  3. package/esm/class/SimpleTable.js +103 -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/loadSample.d.ts +9 -0
  23. package/esm/methods/loadSample.d.ts.map +1 -0
  24. package/esm/methods/loadSample.js +44 -0
  25. package/esm/methods/writeGeoDataQuery.d.ts.map +1 -1
  26. package/esm/methods/writeGeoDataQuery.js +4 -1
  27. package/package.json +1 -1
  28. package/script/class/SimpleTable.d.ts +76 -21
  29. package/script/class/SimpleTable.d.ts.map +1 -1
  30. package/script/class/SimpleTable.js +103 -98
  31. package/script/helpers/findGeoColumn.js +1 -1
  32. package/script/helpers/getProjection.d.ts.map +1 -1
  33. package/script/helpers/getProjection.js +3 -0
  34. package/script/helpers/hasGeometryColumn.d.ts +20 -0
  35. package/script/helpers/hasGeometryColumn.d.ts.map +1 -0
  36. package/script/helpers/hasGeometryColumn.js +24 -0
  37. package/script/helpers/writeGeoData.d.ts +9 -0
  38. package/script/helpers/writeGeoData.d.ts.map +1 -0
  39. package/script/helpers/writeGeoData.js +116 -0
  40. package/script/methods/fuzzyClean.d.ts +2 -2
  41. package/script/methods/fuzzyClean.d.ts.map +1 -1
  42. package/script/methods/fuzzyClean.js +25 -6
  43. package/script/methods/fuzzyJoin.d.ts +2 -2
  44. package/script/methods/fuzzyJoin.d.ts.map +1 -1
  45. package/script/methods/fuzzyJoin.js +3 -3
  46. package/script/methods/fuzzyJoinQuery.d.ts +1 -1
  47. package/script/methods/fuzzyJoinQuery.d.ts.map +1 -1
  48. package/script/methods/fuzzyJoinQuery.js +13 -3
  49. package/script/methods/loadSample.d.ts +9 -0
  50. package/script/methods/loadSample.d.ts.map +1 -0
  51. package/script/methods/loadSample.js +47 -0
  52. package/script/methods/writeGeoDataQuery.d.ts.map +1 -1
  53. 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,16 +90,16 @@ 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"));
103
99
  const createFtsIndex_js_1 = __importDefault(require("../methods/createFtsIndex.js"));
104
100
  const createVssIndex_js_1 = __importDefault(require("../methods/createVssIndex.js"));
105
101
  const bm25_js_1 = __importDefault(require("../methods/bm25.js"));
102
+ const loadSample_js_1 = __importDefault(require("../methods/loadSample.js"));
106
103
  const normalizeString_js_1 = __importDefault(require("../methods/normalizeString.js"));
107
104
  /**
108
105
  * IMPORTANT: When extending this class, always use `this.sdb.newTable()` to
@@ -427,7 +424,13 @@ class SimpleTable extends Simple_js_1.default {
427
424
  *
428
425
  * @example
429
426
  * ```ts
430
- * // Load geospatial data from a shapefile and reproject to WGS84
427
+ * // Load geospatial data from a shapefile (with relevant files in the same folder) and reproject to WGS84
428
+ * await table.loadGeoData("./some-data/some-data.shp", { toWGS84: true });
429
+ * ```
430
+ *
431
+ * @example
432
+ * ```ts
433
+ * // Load geospatial data from a zipped shapefile and reproject to WGS84
431
434
  * await table.loadGeoData("./some-data.shp.zip", { toWGS84: true });
432
435
  * ```
433
436
  */
@@ -811,6 +814,32 @@ class SimpleTable extends Simple_js_1.default {
811
814
  }
812
815
  }
813
816
  }
817
+ /**
818
+ * Fetches sample data from the simple-data-analysis-core GitHub repository.
819
+ *
820
+ * @param sample - The name of the sample to load.
821
+ *
822
+ * Tabular data:
823
+ * - "fires": [firesCanada2023.csv](https://raw.githubusercontent.com/nshiab/simple-data-analysis-core/refs/heads/main/test/geodata/files/firesCanada2023.csv)
824
+ * - "recipes": [recipes.parquet](https://github.com/nshiab/simple-data-analysis-core/raw/refs/heads/main/test/data/files/recipes.parquet)
825
+ * - "temperatures": [dailyTemperatures.csv](https://raw.githubusercontent.com/nshiab/simple-data-analysis-core/refs/heads/main/test/data/files/dailyTemperatures.csv)
826
+ * - "temperaturesCities": [cities.csv](https://raw.githubusercontent.com/nshiab/simple-data-analysis-core/refs/heads/main/test/data/files/cities.csv)
827
+ *
828
+ * Geospatial data:
829
+ * - "canada": [CanadianProvincesAndTerritories.json](https://raw.githubusercontent.com/nshiab/simple-data-analysis-core/refs/heads/main/test/geodata/files/CanadianProvincesAndTerritories.json)
830
+ * - "firesGeo": [firesCanada2023.geojson](https://raw.githubusercontent.com/nshiab/simple-data-analysis-core/refs/heads/main/test/geodata/files/firesCanada2023.geojson)
831
+ *
832
+ * @category Importing Data
833
+ *
834
+ * @example
835
+ * ```ts
836
+ * // Load the fires sample data
837
+ * await table.loadSample("fires");
838
+ * ```
839
+ */
840
+ async loadSample(sample) {
841
+ return (await (0, loadSample_js_1.default)(this, sample));
842
+ }
814
843
  /**
815
844
  * Returns a new table with the same structure and data as this table. The data can be optionally filtered.
816
845
  * Note that cloning large tables can be a slow operation.
@@ -1908,48 +1937,57 @@ class SimpleTable extends Simple_js_1.default {
1908
1937
  * @param rightTable - The SimpleTable instance to be joined with this table.
1909
1938
  * @param leftColumn - The name of the column in this (left) table containing the text to compare.
1910
1939
  * @param rightColumn - The name of the column in the right table containing the text to compare.
1940
+ * @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
1941
  * @param options - An optional object with configuration options:
1912
1942
  * @param options.method - The rapidfuzz similarity algorithm to use. Defaults to `"ratio"`.
1913
1943
  * - `"ratio"`: Overall similarity (Levenshtein-based).
1914
1944
  * - `"partial_ratio"`: Best partial/substring similarity.
1915
1945
  * - `"token_sort_ratio"`: Similarity after sorting tokens (words), useful for reordered words.
1916
1946
  * - `"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
1947
  * @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
1948
  * @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`.
1949
+ * @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
1950
  * @returns A promise that resolves to a table instance containing the fuzzy-joined data (either the modified current table or a new table).
1921
1951
  * @category Table Operations
1922
1952
  *
1923
1953
  * @example
1924
1954
  * ```ts
1925
- * // Fuzzy left join tableA with tableB on 'name' (left) and 'standardName' (right) (ratio >= 80)
1926
- * await tableA.fuzzyJoin(tableB, "name", "standardName");
1955
+ * // Fuzzy left join tableA with tableB on 'name' (left) and 'standardName' (right) with a threshold of 80
1956
+ * // A length-based pre-filter is automatically applied.
1957
+ * await tableA.fuzzyJoin(tableB, "name", "standardName", 80);
1958
+ * ```
1959
+ *
1960
+ * @example
1961
+ * ```ts
1962
+ * // Fuzzy join with a prefix-based pre-filter and a threshold of 80
1963
+ * await tableA.fuzzyJoin(tableB, "name", "standardName", 80, {
1964
+ * preFilterPrefixLen: 3, // Must share the same first 3 characters
1965
+ * });
1927
1966
  * ```
1928
1967
  *
1929
1968
  * @example
1930
1969
  * ```ts
1931
1970
  * // Fuzzy join with a custom threshold and method, storing results in a new table
1932
- * const tableC = await tableA.fuzzyJoin(tableB, "name", "standardName", {
1971
+ * const tableC = await tableA.fuzzyJoin(tableB, "name", "standardName", 90, {
1933
1972
  * method: "token_sort_ratio",
1934
- * threshold: 90,
1935
1973
  * outputTable: "tableC",
1936
1974
  * });
1937
1975
  * ```
1938
1976
  *
1939
1977
  * @example
1940
1978
  * ```ts
1941
- * // Fuzzy join with a custom similarity column name
1942
- * await tableA.fuzzyJoin(tableB, "name", "standardName", {
1979
+ * // Fuzzy join with a custom similarity column name and a threshold of 80
1980
+ * await tableA.fuzzyJoin(tableB, "name", "standardName", 80, {
1943
1981
  * similarityColumn: "matchScore",
1944
1982
  * });
1945
1983
  * ```
1946
1984
  */
1947
- async fuzzyJoin(rightTable, leftColumn, rightColumn, options = {}) {
1985
+ async fuzzyJoin(rightTable, leftColumn, rightColumn, threshold, options = {}) {
1948
1986
  if (options.outputTable === true) {
1949
1987
  options.outputTable = `table${this.sdb.tableIncrement}`;
1950
1988
  this.sdb.tableIncrement += 1;
1951
1989
  }
1952
- return await (0, fuzzyJoin_js_1.default)(this, rightTable, leftColumn, rightColumn, options);
1990
+ return await (0, fuzzyJoin_js_1.default)(this, rightTable, leftColumn, rightColumn, threshold, options);
1953
1991
  }
1954
1992
  /**
1955
1993
  * Normalizes string values in a column by detecting fuzzy duplicates and replacing them with a single canonical value.
@@ -1963,42 +2001,52 @@ class SimpleTable extends Simple_js_1.default {
1963
2001
  *
1964
2002
  * @param column - The name of the column containing the strings to normalize.
1965
2003
  * @param newColumn - The name of the column to write the normalized values to. Use the same name as `column` to normalize in-place.
2004
+ * @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
2005
  * @param options - An optional object with configuration options:
1967
2006
  * @param options.method - The rapidfuzz similarity algorithm to use. Defaults to `"ratio"`.
1968
2007
  * - `"ratio"`: Overall similarity.
1969
2008
  * - `"partial_ratio"`: Best partial/substring similarity.
1970
2009
  * - `"token_sort_ratio"`: Similarity after sorting tokens (words), useful for reordered words.
1971
2010
  * - `"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
2011
  * @param options.keep - The strategy for choosing the canonical value within each cluster of similar strings. Defaults to `"mostCommon"`.
1974
2012
  * - `"mostCommon"`: Keep the value that appears most frequently in the original column.
1975
2013
  * - `"longestString"`: Keep the longest string in the cluster.
1976
2014
  * - `"shortestString"`: Keep the shortest string in the cluster.
1977
2015
  * - `"mostCentral"`: Keep the string with the highest total similarity score to all other cluster members (the most "central" string).
1978
2016
  * - `"maxScore"`: Keep the string that participates in the single highest-scoring pairwise match within the cluster.
2017
+ * @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
2018
  * @returns A promise that resolves when the column has been normalized.
1980
2019
  * @category Updating Data
1981
2020
  *
1982
2021
  * @example
1983
2022
  * ```ts
1984
- * // Normalize 'city' into a new 'cityClean' column, keeping the most common string per cluster
1985
- * await table.fuzzyClean("city", "cityClean");
2023
+ * // Normalize 'city' into a new 'cityClean' column, keeping the most common string per cluster with a threshold of 80
2024
+ * // A length-based pre-filter is automatically applied.
2025
+ * await table.fuzzyClean("city", "cityClean", 80);
2026
+ * ```
2027
+ *
2028
+ * @example
2029
+ * ```ts
2030
+ * // Normalize with a prefix-based pre-filter and a threshold of 80
2031
+ * await table.fuzzyClean("city", "cityClean", 80, {
2032
+ * preFilterPrefixLen: 5, // Must share the same first 5 characters
2033
+ * });
1986
2034
  * ```
1987
2035
  *
1988
2036
  * @example
1989
2037
  * ```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 });
2038
+ * // Normalize 'companyName' into a new column using token_sort_ratio and a threshold of 90
2039
+ * await table.fuzzyClean("companyName", "companyNameClean", 90, { method: "token_sort_ratio" });
1992
2040
  * ```
1993
2041
  *
1994
2042
  * @example
1995
2043
  * ```ts
1996
- * // Normalize 'category' in-place, keeping the longest string in each cluster
1997
- * await table.fuzzyClean("category", "category", { keep: "longestString" });
2044
+ * // Normalize 'category' in-place, keeping the longest string in each cluster and a threshold of 80
2045
+ * await table.fuzzyClean("category", "category", 80, { keep: "longestString" });
1998
2046
  * ```
1999
2047
  */
2000
- async fuzzyClean(column, newColumn, options = {}) {
2001
- await (0, fuzzyClean_js_1.default)(this, column, newColumn, options);
2048
+ async fuzzyClean(column, newColumn, threshold, options = {}) {
2049
+ await (0, fuzzyClean_js_1.default)(this, column, newColumn, threshold, options);
2002
2050
  }
2003
2051
  /**
2004
2052
  * Replaces specified strings in the selected columns.
@@ -3930,6 +3978,9 @@ class SimpleTable extends Simple_js_1.default {
3930
3978
  * ```
3931
3979
  */
3932
3980
  async getData(options = {}) {
3981
+ if (await (0, hasGeometryColumn_js_1.default)(this)) {
3982
+ throw new Error("Table contains geometry columns. Use getGeoData() instead.");
3983
+ }
3933
3984
  const columns = options.columns
3934
3985
  ? (typeof options.columns === "string"
3935
3986
  ? [options.columns]
@@ -3994,8 +4045,9 @@ class SimpleTable extends Simple_js_1.default {
3994
4045
  * ```
3995
4046
  */
3996
4047
  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, {
4048
+ await (0, queryDB_js_1.default)(this, (await this.getColumns()).includes(newColumn)
4049
+ ? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Point2D("${columnLat}", "${columnLon}")`
4050
+ : `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
4051
  table: this.name,
4000
4052
  method: "points()",
4001
4053
  parameters: { columnLat, columnLon, newColumn },
@@ -4411,7 +4463,9 @@ class SimpleTable extends Simple_js_1.default {
4411
4463
  const column = typeof options.column === "string"
4412
4464
  ? options.column
4413
4465
  : 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, {
4466
+ await (0, queryDB_js_1.default)(this, (await this.getColumns()).includes(newColumn)
4467
+ ? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Buffer("${column}", ${distance})`
4468
+ : `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
4469
  table: this.name,
4416
4470
  method: "buffer()",
4417
4471
  parameters: { column, newColumn, distance },
@@ -4497,7 +4551,9 @@ class SimpleTable extends Simple_js_1.default {
4497
4551
  if (this.projections[column1] !== this.projections[column2]) {
4498
4552
  throw new Error(`${column1} and ${column2} don't have the same projection.\n${column1}: ${this.projections[column1]}\n${column2}: ${this.projections[column2]}`);
4499
4553
  }
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, {
4554
+ await (0, queryDB_js_1.default)(this, (await this.getColumns()).includes(newColumn)
4555
+ ? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Intersection("${column1}", "${column2}")`
4556
+ : `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
4557
  table: this.name,
4502
4558
  method: "intersection()",
4503
4559
  parameters: { column1, column2, newColumn },
@@ -4622,7 +4678,9 @@ class SimpleTable extends Simple_js_1.default {
4622
4678
  if (this.projections[column1] !== this.projections[column2]) {
4623
4679
  throw new Error(`${column1} and ${column2} don't have the same projection.\n${column1}: ${this.projections[column1]}\n${column2}: ${this.projections[column2]}`);
4624
4680
  }
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, {
4681
+ await (0, queryDB_js_1.default)(this, (await this.getColumns()).includes(newColumn)
4682
+ ? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Union("${column1}", "${column2}")`
4683
+ : `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
4684
  table: this.name,
4627
4685
  method: "union()",
4628
4686
  parameters: { column1, column2, newColumn },
@@ -4713,7 +4771,9 @@ class SimpleTable extends Simple_js_1.default {
4713
4771
  const column = typeof options.column === "string"
4714
4772
  ? options.column
4715
4773
  : 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, {
4774
+ await (0, queryDB_js_1.default)(this, (await this.getColumns()).includes(newColumn)
4775
+ ? `INSTALL spatial; LOAD spatial; UPDATE "${this.name}" SET "${newColumn}" = ST_Centroid("${column}")`
4776
+ : `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
4777
  table: this.name,
4718
4778
  method: "centroid()",
4719
4779
  parameters: { column, newColumn },
@@ -4998,6 +5058,9 @@ class SimpleTable extends Simple_js_1.default {
4998
5058
  * ```
4999
5059
  */
5000
5060
  async writeData(file, options = {}) {
5061
+ if (await (0, hasGeometryColumn_js_1.default)(this)) {
5062
+ throw new Error("Table contains geometry columns. Use writeGeoData() instead.");
5063
+ }
5001
5064
  (0, createDirectory_js_1.default)(file);
5002
5065
  const extension = (0, getExtension_js_1.default)(file);
5003
5066
  if (options.dataAsArrays) {
@@ -5012,12 +5075,12 @@ class SimpleTable extends Simple_js_1.default {
5012
5075
  }
5013
5076
  }
5014
5077
  /**
5015
- * Writes the table's geospatial data to a file in GeoJSON or GeoParquet format.
5078
+ * Writes the table's geospatial data to a file in GeoJSON, GeoParquet, or Shapefile format.
5016
5079
  * If the specified path does not exist, it will be created.
5017
5080
  *
5018
5081
  * 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
5082
  *
5020
- * @param file - The absolute path to the output file (e.g., `"./output.geojson"`, `"./output.geoparquet"`).
5083
+ * @param file - The absolute path to the output file (e.g., `"./output.geojson"`, `"./output.geoparquet"`, `"./shapefile-folder/output.shp"`).
5021
5084
  * @param options - An optional object with configuration options:
5022
5085
  * @param options.precision - For GeoJSON, the maximum number of figures after the decimal separator to write in coordinates. Defaults to `undefined` (full precision).
5023
5086
  * @param options.compression - For GeoParquet, if `true`, the output will be ZSTD compressed. Defaults to `false`.
@@ -5041,6 +5104,12 @@ class SimpleTable extends Simple_js_1.default {
5041
5104
  *
5042
5105
  * @example
5043
5106
  * ```ts
5107
+ * // Write geospatial data to a Shapefile with all relevant files in the same folder
5108
+ * await table.writeGeoData("./shapefile-folder/output.shp");
5109
+ * ```
5110
+ *
5111
+ * @example
5112
+ * ```ts
5044
5113
  * // Write GeoJSON with specific precision and metadata
5045
5114
  * await table.writeGeoData("./output_high_precision.geojson", {
5046
5115
  * precision: 6,
@@ -5049,71 +5118,7 @@ class SimpleTable extends Simple_js_1.default {
5049
5118
  * ```
5050
5119
  */
5051
5120
  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
- }
5121
+ await (0, writeGeoData_js_1.default)(this, file, options);
5117
5122
  }
5118
5123
  /**
5119
5124
  * 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"}