diff --git a/MEOS.NET.Tests/ScalarArrayTests.cs b/MEOS.NET.Tests/ScalarArrayTests.cs
new file mode 100644
index 0000000..b80e2e0
--- /dev/null
+++ b/MEOS.NET.Tests/ScalarArrayTests.cs
@@ -0,0 +1,37 @@
+using MEOS.NET.Types;
+
+namespace MEOS.NET.Tests
+{
+ ///
+ /// An array of scalars is read at the scalar's own width. Reading one as an
+ /// array of pointers walks it eight bytes at a step, which runs off the end
+ /// of a bool array and answers the values' own bytes as addresses.
+ ///
+ [TestClass]
+ public class ScalarArrayTests : MeosTest
+ {
+ [TestMethod]
+ public void ATemporalBooleanAnswersItsValuesAsBooleans()
+ {
+ TBool temp = (TBool)TBool.In(
+ "{true@2024-12-06, false@2024-12-07, true@2024-12-08}")!;
+
+ bool[] values = temp.Values();
+
+ Assert.AreEqual(2, values.Length);
+ CollectionAssert.AreEquivalent(new[] { false, true }, values);
+ }
+
+ [TestMethod]
+ public void ATemporalFloatAnswersItsValuesAsDoubles()
+ {
+ TFloat temp = (TFloat)TFloat.In(
+ "{1.5@2024-12-06, 2.5@2024-12-07}")!;
+
+ double[] values = temp.Values();
+
+ Assert.AreEqual(2, values.Length);
+ CollectionAssert.AreEquivalent(new[] { 1.5, 2.5 }, values);
+ }
+ }
+}
diff --git a/MEOS.NET/Functions/Meos.meos.g.cs b/MEOS.NET/Functions/Meos.meos.g.cs
index f6ded9e..757a524 100644
--- a/MEOS.NET/Functions/Meos.meos.g.cs
+++ b/MEOS.NET/Functions/Meos.meos.g.cs
@@ -2905,16 +2905,16 @@ public static bool TboolValueAtTimestamptz(IntPtr temp, long t, bool strict, Int
public static bool TboolValueN(IntPtr temp, int n, IntPtr result)
=> SafeExecution(() => Native.TboolValueN(temp, n, result));
- public static IntPtr[] TboolValues(IntPtr temp)
+ public static bool[] TboolValues(IntPtr temp)
{
IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
try
{
IntPtr _p = SafeExecution(() => Native.TboolValues(temp, _cnt));
int _n = Marshal.ReadInt32(_cnt);
- IntPtr[] _out = new IntPtr[_n];
+ bool[] _out = new bool[_n];
for (int _i = 0; _i < _n; _i++)
- { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }
+ { _out[_i] = Marshal.ReadByte(_p, _i) != 0; }
return _out;
}
finally { Marshal.FreeHGlobal(_cnt); }
diff --git a/MEOS.NET/Functions/Meos.meos_geo.g.cs b/MEOS.NET/Functions/Meos.meos_geo.g.cs
index 8a08333..d56ab8a 100644
--- a/MEOS.NET/Functions/Meos.meos_geo.g.cs
+++ b/MEOS.NET/Functions/Meos.meos_geo.g.cs
@@ -1736,16 +1736,16 @@ public static int[] GeoClusterKmeans(IntPtr geoms, uint ngeoms, uint k)
finally { Marshal.FreeHGlobal(_cnt); }
}
- public static IntPtr[] GeoClusterDbscan(IntPtr geoms, uint ngeoms, double tolerance, int minpoints)
+ public static uint[] GeoClusterDbscan(IntPtr geoms, uint ngeoms, double tolerance, int minpoints)
{
IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
try
{
IntPtr _p = SafeExecution(() => Native.GeoClusterDbscan(geoms, ngeoms, tolerance, minpoints, _cnt));
int _n = Marshal.ReadInt32(_cnt);
- IntPtr[] _out = new IntPtr[_n];
+ uint[] _out = new uint[_n];
for (int _i = 0; _i < _n; _i++)
- { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }
+ { _out[_i] = (uint) Marshal.ReadInt32(_p, _i * 4); }
return _out;
}
finally { Marshal.FreeHGlobal(_cnt); }
diff --git a/MEOS.NET/Functions/Meos.meos_h3.g.cs b/MEOS.NET/Functions/Meos.meos_h3.g.cs
index 3d047f9..729cb82 100644
--- a/MEOS.NET/Functions/Meos.meos_h3.g.cs
+++ b/MEOS.NET/Functions/Meos.meos_h3.g.cs
@@ -135,16 +135,16 @@ public static ulong Th3indexEndValue(IntPtr temp)
public static bool Th3indexValueN(IntPtr temp, int n, IntPtr result)
=> SafeExecution(() => Native.Th3indexValueN(temp, n, result));
- public static IntPtr[] Th3indexValues(IntPtr temp)
+ public static ulong[] Th3indexValues(IntPtr temp)
{
IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
try
{
IntPtr _p = SafeExecution(() => Native.Th3indexValues(temp, _cnt));
int _n = Marshal.ReadInt32(_cnt);
- IntPtr[] _out = new IntPtr[_n];
+ ulong[] _out = new ulong[_n];
for (int _i = 0; _i < _n; _i++)
- { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }
+ { _out[_i] = (ulong) Marshal.ReadInt64(_p, _i * 8); }
return _out;
}
finally { Marshal.FreeHGlobal(_cnt); }
diff --git a/MEOS.NET/Functions/Meos.meos_json.g.cs b/MEOS.NET/Functions/Meos.meos_json.g.cs
index 10f7a4c..27542e7 100644
--- a/MEOS.NET/Functions/Meos.meos_json.g.cs
+++ b/MEOS.NET/Functions/Meos.meos_json.g.cs
@@ -462,16 +462,16 @@ public static IntPtr JsonbsetDelete(IntPtr set, IntPtr key)
public static IntPtr JsonbsetDeleteArray(IntPtr set, IntPtr keys, int count)
=> SafeExecution(() => Native.JsonbsetDeleteArray(set, keys, count));
- public static IntPtr[] JsonbsetExists(IntPtr set, IntPtr key)
+ public static bool[] JsonbsetExists(IntPtr set, IntPtr key)
{
IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
try
{
IntPtr _p = SafeExecution(() => Native.JsonbsetExists(set, key, _cnt));
int _n = Marshal.ReadInt32(_cnt);
- IntPtr[] _out = new IntPtr[_n];
+ bool[] _out = new bool[_n];
for (int _i = 0; _i < _n; _i++)
- { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }
+ { _out[_i] = Marshal.ReadByte(_p, _i) != 0; }
return _out;
}
finally { Marshal.FreeHGlobal(_cnt); }
@@ -513,31 +513,31 @@ public static IntPtr JsonbsetExtractPath(IntPtr set, IntPtr path_elems, int path
public static IntPtr JsonbsetInsert(IntPtr set, IntPtr path_elems, int path_len, IntPtr newjb, bool after)
=> SafeExecution(() => Native.JsonbsetInsert(set, path_elems, path_len, newjb, after));
- public static IntPtr[] JsonbsetPathExists(IntPtr set, IntPtr jp, IntPtr vars, bool silent, bool tz)
+ public static bool[] JsonbsetPathExists(IntPtr set, IntPtr jp, IntPtr vars, bool silent, bool tz)
{
IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
try
{
IntPtr _p = SafeExecution(() => Native.JsonbsetPathExists(set, jp, vars, silent, tz, _cnt));
int _n = Marshal.ReadInt32(_cnt);
- IntPtr[] _out = new IntPtr[_n];
+ bool[] _out = new bool[_n];
for (int _i = 0; _i < _n; _i++)
- { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }
+ { _out[_i] = Marshal.ReadByte(_p, _i) != 0; }
return _out;
}
finally { Marshal.FreeHGlobal(_cnt); }
}
- public static IntPtr[] JsonbsetPathMatch(IntPtr set, IntPtr jp, IntPtr vars, bool silent, bool tz)
+ public static bool[] JsonbsetPathMatch(IntPtr set, IntPtr jp, IntPtr vars, bool silent, bool tz)
{
IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
try
{
IntPtr _p = SafeExecution(() => Native.JsonbsetPathMatch(set, jp, vars, silent, tz, _cnt));
int _n = Marshal.ReadInt32(_cnt);
- IntPtr[] _out = new IntPtr[_n];
+ bool[] _out = new bool[_n];
for (int _i = 0; _i < _n; _i++)
- { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }
+ { _out[_i] = Marshal.ReadByte(_p, _i) != 0; }
return _out;
}
finally { Marshal.FreeHGlobal(_cnt); }
diff --git a/MEOS.NET/Functions/Meos.meos_quadbin.g.cs b/MEOS.NET/Functions/Meos.meos_quadbin.g.cs
index 22e4723..43c2806 100644
--- a/MEOS.NET/Functions/Meos.meos_quadbin.g.cs
+++ b/MEOS.NET/Functions/Meos.meos_quadbin.g.cs
@@ -57,16 +57,16 @@ public static uint QuadbinGetResolution(ulong cell)
public static ulong QuadbinCellToParent(ulong cell, uint parent_resolution)
=> SafeExecution(() => Native.QuadbinCellToParent(cell, parent_resolution));
- public static IntPtr[] QuadbinCellToChildren(ulong cell, uint children_resolution)
+ public static ulong[] QuadbinCellToChildren(ulong cell, uint children_resolution)
{
IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
try
{
IntPtr _p = SafeExecution(() => Native.QuadbinCellToChildren(cell, children_resolution, _cnt));
int _n = Marshal.ReadInt32(_cnt);
- IntPtr[] _out = new IntPtr[_n];
+ ulong[] _out = new ulong[_n];
for (int _i = 0; _i < _n; _i++)
- { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }
+ { _out[_i] = (ulong) Marshal.ReadInt64(_p, _i * 8); }
return _out;
}
finally { Marshal.FreeHGlobal(_cnt); }
@@ -75,16 +75,16 @@ public static IntPtr[] QuadbinCellToChildren(ulong cell, uint children_resolutio
public static ulong QuadbinCellSibling(ulong cell, string direction)
=> SafeExecution(() => Native.QuadbinCellSibling(cell, direction));
- public static IntPtr[] QuadbinKRing(ulong cell, int k)
+ public static ulong[] QuadbinKRing(ulong cell, int k)
{
IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
try
{
IntPtr _p = SafeExecution(() => Native.QuadbinKRing(cell, k, _cnt));
int _n = Marshal.ReadInt32(_cnt);
- IntPtr[] _out = new IntPtr[_n];
+ ulong[] _out = new ulong[_n];
for (int _i = 0; _i < _n; _i++)
- { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }
+ { _out[_i] = (ulong) Marshal.ReadInt64(_p, _i * 8); }
return _out;
}
finally { Marshal.FreeHGlobal(_cnt); }
@@ -162,16 +162,16 @@ public static ulong TquadbinEndValue(IntPtr temp)
public static bool TquadbinValueN(IntPtr temp, int n, IntPtr result)
=> SafeExecution(() => Native.TquadbinValueN(temp, n, result));
- public static IntPtr[] TquadbinValues(IntPtr temp)
+ public static ulong[] TquadbinValues(IntPtr temp)
{
IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
try
{
IntPtr _p = SafeExecution(() => Native.TquadbinValues(temp, _cnt));
int _n = Marshal.ReadInt32(_cnt);
- IntPtr[] _out = new IntPtr[_n];
+ ulong[] _out = new ulong[_n];
for (int _i = 0; _i < _n; _i++)
- { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }
+ { _out[_i] = (ulong) Marshal.ReadInt64(_p, _i * 8); }
return _out;
}
finally { Marshal.FreeHGlobal(_cnt); }
diff --git a/MEOS.NET/Functions/Meos.meos_raster.g.cs b/MEOS.NET/Functions/Meos.meos_raster.g.cs
index d585ecd..5fdcb90 100644
--- a/MEOS.NET/Functions/Meos.meos_raster.g.cs
+++ b/MEOS.NET/Functions/Meos.meos_raster.g.cs
@@ -147,16 +147,16 @@ public static IntPtr RasterTileValue(IntPtr traj, IntPtr rq)
public static IntPtr RasterTileValueArray(IntPtr traj, IntPtr rqarr, int count)
=> SafeExecution(() => Native.RasterTileValueArray(traj, rqarr, count));
- public static IntPtr[] TrajectoryQuadbins(IntPtr traj, uint zoom)
+ public static ulong[] TrajectoryQuadbins(IntPtr traj, uint zoom)
{
IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
try
{
IntPtr _p = SafeExecution(() => Native.TrajectoryQuadbins(traj, zoom, _cnt));
int _n = Marshal.ReadInt32(_cnt);
- IntPtr[] _out = new IntPtr[_n];
+ ulong[] _out = new ulong[_n];
for (int _i = 0; _i < _n; _i++)
- { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }
+ { _out[_i] = (ulong) Marshal.ReadInt64(_p, _i * 8); }
return _out;
}
finally { Marshal.FreeHGlobal(_cnt); }
diff --git a/MEOS.NET/Types/Geo.g.cs b/MEOS.NET/Types/Geo.g.cs
index 6993294..266d0e7 100644
--- a/MEOS.NET/Types/Geo.g.cs
+++ b/MEOS.NET/Types/Geo.g.cs
@@ -114,6 +114,25 @@ public ulong ToS2cellCell(int level)
public STBox? TstzspanToStbox(Span s)
=> MEOSFactory.WrapSTBox(Meos.GeoTstzspanToStbox(this.Ptr, s.Ptr));
+ public static uint[] ClusterDbscan(Geo[] geoms, double tolerance, int minpoints)
+ {
+ IntPtr[] _geomsValues = new IntPtr[geoms.Length];
+ for (int i = 0; i < geoms.Length; i++)
+ {
+ _geomsValues[i] = geoms[i].Ptr;
+ }
+
+ GCHandle _geoms = GCHandle.Alloc(_geomsValues, GCHandleType.Pinned);
+ try
+ {
+ return Meos.GeoClusterDbscan(_geoms.AddrOfPinnedObject(), (uint) geoms.Length, tolerance, minpoints);
+ }
+ finally
+ {
+ _geoms.Free();
+ }
+ }
+
public static Geo?[] ClusterIntersecting(Geo[] geoms)
{
IntPtr[] _geomsValues = new IntPtr[geoms.Length];
diff --git a/MEOS.NET/Types/TBool.g.cs b/MEOS.NET/Types/TBool.g.cs
index c75c7db..f1e9e2e 100644
--- a/MEOS.NET/Types/TBool.g.cs
+++ b/MEOS.NET/Types/TBool.g.cs
@@ -72,6 +72,9 @@ public bool StartValue()
}
}
+ public bool[] Values()
+ => Meos.TboolValues(this.Ptr);
+
public SpanSet? WhenTrue()
=> MEOSFactory.WrapSpanSet(Meos.TboolWhenTrue(this.Ptr));
diff --git a/tools/codegen.py b/tools/codegen.py
index 39222bb..a954f0c 100755
--- a/tools/codegen.py
+++ b/tools/codegen.py
@@ -433,6 +433,20 @@ def gen_external_functions(funcs: list[dict]) -> str:
"short": "short",
}
+# How to read one element of a scalar array `Marshal.Copy` has no overload for.
+# `{0}` is the array's base pointer and `{1}` the byte offset of the element.
+# Reading such an array as an array of POINTERS walks it at eight bytes a step,
+# which runs off the end of a `bool` array and answers addresses that are the
+# values' own bytes.
+_SCALAR_ELEM_READER: dict[str, tuple[int, str]] = {
+ "bool": (1, "Marshal.ReadByte({0}, {1}) != 0"),
+ "sbyte": (1, "(sbyte) Marshal.ReadByte({0}, {1})"),
+ "ushort": (2, "(ushort) Marshal.ReadInt16({0}, {1})"),
+ "uint": (4, "(uint) Marshal.ReadInt32({0}, {1})"),
+ "ulong": (8, "(ulong) Marshal.ReadInt64({0}, {1})"),
+ "float": (4, "BitConverter.Int32BitsToSingle(Marshal.ReadInt32({0}, {1}))"),
+}
+
def _strip_const_stars(c_type: str) -> tuple[str, int]:
s = c_type.replace("const ", "").strip()
@@ -462,6 +476,8 @@ def _csharp_array_element(c_type: str, canonical: str) -> tuple[str, str]:
return (elem, "Marshal.Copy")
if base == "uint8_t":
return ("byte", "ByteBuffer")
+ if elem in _SCALAR_ELEM_READER:
+ return (elem, f"ScalarArray:{elem}")
if stars == 1 and base in STRUCTS:
# A single pointer to a catalog struct is an array of struct VALUES, not
# of pointers: element i sits at the struct's own stride, and reading it
@@ -567,6 +583,12 @@ def _emit_outputs_wrapper(f: dict) -> list[str]:
stride = ret_strategy.split(":", 1)[1]
lines.append(" for (int _i = 0; _i < _n; _i++)")
lines.append(f" {{ _resultArr[_i] = IntPtr.Add(_resultPtr, _i * {stride}); }}")
+ elif ret_strategy.startswith("ScalarArray:"):
+ size, reader = _SCALAR_ELEM_READER[ret_strategy.split(":", 1)[1]]
+ offset = "_i" if size == 1 else f"_i * {size}"
+ lines.append(" for (int _i = 0; _i < _n; _i++)")
+ lines.append(f" {{ _resultArr[_i] = "
+ f"{reader.format('_resultPtr', offset)}; }}")
else:
lines.append(" for (int _i = 0; _i < _n; _i++)")
lines.append(" { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); }")
@@ -577,6 +599,12 @@ def _emit_outputs_wrapper(f: dict) -> list[str]:
stride = strategy.split(":", 1)[1]
lines.append(f" for (int _i = 0; _i < _n; _i++)")
lines.append(f" {{ _{local}_out[_i] = IntPtr.Add(_{local}_arr, _i * {stride}); }}")
+ elif strategy.startswith("ScalarArray:"):
+ size, reader = _SCALAR_ELEM_READER[strategy.split(":", 1)[1]]
+ offset = "_i" if size == 1 else f"_i * {size}"
+ lines.append(f" for (int _i = 0; _i < _n; _i++)")
+ lines.append(f" {{ _{local}_out[_i] = "
+ f"{reader.format(f'_{local}_arr', offset)}; }}")
elif elem == "IntPtr":
lines.append(f" for (int _i = 0; _i < _n; _i++)")
lines.append(f" {{ _{local}_out[_i] = Marshal.ReadIntPtr(_{local}_arr, _i * IntPtr.Size); }}")
@@ -623,6 +651,11 @@ def _copy(indent: str) -> list[str]:
stride = strategy.split(":", 1)[1]
out.append(f"{indent}for (int _i = 0; _i < _n; _i++)")
out.append(f"{indent}{{ _out[_i] = IntPtr.Add(_p, _i * {stride}); }}")
+ elif strategy.startswith("ScalarArray:"):
+ size, reader = _SCALAR_ELEM_READER[strategy.split(":", 1)[1]]
+ offset = "_i" if size == 1 else f"_i * {size}"
+ out.append(f"{indent}for (int _i = 0; _i < _n; _i++)")
+ out.append(f"{indent}{{ _out[_i] = {reader.format('_p', offset)}; }}")
else:
out.append(f"{indent}for (int _i = 0; _i < _n; _i++)")
out.append(f"{indent}{{ _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }}")
diff --git a/tools/objectgen.py b/tools/objectgen.py
index 1fc3856..2d5ed65 100644
--- a/tools/objectgen.py
+++ b/tools/objectgen.py
@@ -336,7 +336,9 @@ def map_return(self, f: dict, wrapper_ret: str) -> tuple[str, str] | None:
elem = self.m.class_for_ctype(c[:-1] if c.endswith(" **") else c)
if elem:
return (f"{elem}?[]", f"MEOSFactory.Wrap{elem}Array($)")
- if wrapper_ret in ("long[]", "int[]", "double[]", "byte[]"):
+ # An array of scalars is handed on as it stands, whichever scalar it is
+ # — the wrapper has already read it at the element's own width.
+ if wrapper_ret.endswith("[]") and is_scalar(wrapper_ret[:-2]):
return (wrapper_ret, "$")
if c == "void *" and wrapper_ret == "IntPtr":
return ("IntPtr", "$")