-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Npgsql: Use NpgsqlPoint
.NET type for marshalling GEO_POINT
types. Explore communicating and marshalling GeoJSON types.
#782
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
using System.Data; | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
using GeoJSON.Net.Geometry; | ||
using Newtonsoft.Json; | ||
using Npgsql; | ||
using NpgsqlTypes; | ||
|
@@ -166,12 +167,27 @@ INSERT INTO testdrive.example ( | |
cmd.Parameters.AddWithValue("timestamp_tz", "1970-01-02T00:00:00+01:00"); | ||
cmd.Parameters.AddWithValue("timestamp_notz", "1970-01-02T00:00:00"); | ||
cmd.Parameters.AddWithValue("ip", "127.0.0.1"); | ||
|
||
// Container types | ||
cmd.Parameters.AddWithValue("array", NpgsqlDbType.Json, new List<string>{"foo", "bar"}); | ||
cmd.Parameters.AddWithValue("object", NpgsqlDbType.Json, new Dictionary<string, string>{{"foo", "bar"}}); | ||
cmd.Parameters.AddWithValue("geopoint", new List<double>{85.43, 66.23}); | ||
// TODO: Check if `GEO_SHAPE` types can be represented by real .NET or Npgsql data types. | ||
|
||
// Geospatial types | ||
|
||
// GEO_POINT | ||
// Alternatively to `NpgsqlPoint`, you can also use `List<double>{85.43, 66.23}`. | ||
cmd.Parameters.AddWithValue("geopoint", new NpgsqlPoint(85.43, 66.23)); | ||
|
||
// GEO_SHAPE | ||
// While `GEO_POINT` is transparently marshalled as `NpgsqlPoint`, | ||
// `GEO_SHAPE` is communicated as scalar `string` type, using WKT or GeoJSON format. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: CrateDB won't communicate this as Maybe my previously commented workaround by casting the insert to an |
||
// TODO: Possibly support transparently converging `GEO_SHAPE` to one of | ||
// `NpgsqlLSeg`, `NpgsqlBox`, `NpgsqlPath`, `NpgsqlPolygon`, `NpgsqlCircle`. | ||
cmd.Parameters.AddWithValue("geoshape", "POLYGON ((5 5, 10 5, 10 10, 5 10, 5 5))"); | ||
|
||
// Vector type | ||
cmd.Parameters.AddWithValue("float_vector", new List<double> {1.1, 2.2, 3.3}); | ||
|
||
cmd.ExecuteNonQuery(); | ||
} | ||
|
||
|
@@ -336,6 +352,102 @@ public async Task<List<BasicPoco>> ArrayPocoExample() | |
} | ||
} | ||
|
||
public async Task InsertGeoJsonTyped() | ||
{ | ||
/*** | ||
* Verify Npgsql PostGIS/GeoJSON Type Plugin with CrateDB. | ||
* https://www.npgsql.org/doc/types/geojson.html | ||
* | ||
* TODO: Does not work yet, because CrateDB communicates GEO_SHAPE as string? | ||
* The error message is: | ||
Comment on lines
+361
to
+362
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See previous comment about a possible workaround. |
||
* | ||
* System.NotSupportedException : The NpgsqlDbType 'Geometry' isn't present in your | ||
* database. You may need to install an extension or upgrade to a newer version. | ||
*/ | ||
Console.WriteLine("Running InsertGeo"); | ||
|
||
// Insert single data point. | ||
await using (var cmd = new NpgsqlCommand(""" | ||
INSERT INTO testdrive.example ( | ||
"geoshape" | ||
) VALUES ( | ||
@geoshape | ||
); | ||
""", conn)) | ||
{ | ||
var point = new Point(new Position(85.43, 66.23)); | ||
cmd.Parameters.AddWithValue("geoshape", NpgsqlDbType.Geometry, point); | ||
cmd.ExecuteNonQuery(); | ||
} | ||
|
||
// Flush data. | ||
await RefreshTable(); | ||
} | ||
Comment on lines
+355
to
+385
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be sweet to gain typed GeoJSON support, like the Npgsql PostGIS/GeoJSON Type Plugin might be providing it when talking to PostGIS. I don't know why it isn't working, the error message is: System.NotSupportedException : The NpgsqlDbType 'Geometry' isn't present in your
database. You may need to install an extension or upgrade to a newer version. Maybe it does not work, because CrateDB communicates There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is PostGIS' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When talking about PostGIS, and looking at compatibility concerns, it is not just about types, but also, and mostly, about operations on them. PostGIS unlocks GDAL, while CrateDB unlocks JTS. Those are technically different animals, while they are still living in the same habitat. In this spirit, I figure that a simple alias will probably not be applicable, even if it would also be my dearest wish. This doesn't mean we should not explore this area closer, how we could provide downstream compatibility, or at least a reasonable feature parity, possibly by other means. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Maybe @seut has more insights into that. I will be so happy to also learn more about those details, and if they have been parts of any discussions in the past already. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From reading the PostGIS docs, the generic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @amotl @seut I have created crate/crate#17187 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you. 👍 |
||
|
||
public async Task InsertGeoJsonString() | ||
{ | ||
/*** | ||
* Communicate GeoJSON types as strings, marshall from/to GeoJSON types manually. | ||
*/ | ||
Console.WriteLine("Running InsertGeoRaw"); | ||
|
||
// Insert single data point. | ||
await using (var cmd = new NpgsqlCommand(""" | ||
INSERT INTO testdrive.example ( | ||
"geoshape" | ||
) VALUES ( | ||
@geoshape | ||
); | ||
""", conn)) | ||
{ | ||
var point = new Point(new Position(85.43, 66.23)); | ||
var poly = new Polygon([ | ||
new LineString([ | ||
new Position(longitude: 5.0, latitude: 5.0), | ||
new Position(longitude: 5.0, latitude: 10.0), | ||
new Position(longitude: 10.0, latitude: 10.0), | ||
new Position(longitude: 10.0, latitude: 5.0), | ||
new Position(longitude: 5.0, latitude: 5.0), | ||
]) | ||
]); | ||
// TODO: Can GEO_SHAPE types be directly marshalled to a .NET GeoJSON type? | ||
// Currently, `InsertGeoJsonTyped` does not work yet. | ||
cmd.Parameters.AddWithValue("geoshape", NpgsqlDbType.Json, JsonConvert.SerializeObject(point)); | ||
cmd.ExecuteNonQuery(); | ||
|
||
cmd.Parameters.Clear(); | ||
|
||
cmd.Parameters.AddWithValue("geoshape", NpgsqlDbType.Json, JsonConvert.SerializeObject(poly)); | ||
cmd.ExecuteNonQuery(); | ||
Comment on lines
+403
to
+421
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What works is to communicate GeoJSON data using the Contrary to that, as mentioned above, the Npgsql PostGIS/GeoJSON Type Plugin enables to communicate .NET's GeoJSON types natively. @simonprickett: Please let me know if you find any way do that already, which I might not have discovered yet. Thanks! |
||
} | ||
|
||
// Flush data. | ||
await RefreshTable(); | ||
|
||
} | ||
|
||
public async Task<Point?> GeoJsonTypesExample() | ||
{ | ||
Console.WriteLine("Running GeoJsonTypesExample"); | ||
|
||
// Provision data. | ||
await CreateTable(); | ||
// await InsertGeoJsonTyped(); | ||
await InsertGeoJsonString(); | ||
|
||
// Query back data. | ||
await using (var cmd = new NpgsqlCommand("SELECT * FROM testdrive.example", conn)) | ||
await using (var reader = cmd.ExecuteReader()) | ||
{ | ||
reader.Read(); | ||
// TODO: Can GEO_SHAPE types be directly marshalled to a .NET GeoJSON type? | ||
// Currently, `InsertGeoJsonTyped` does not work yet. | ||
var obj = reader.GetFieldValue<JsonDocument>("geoshape"); | ||
var geoJsonObject = JsonConvert.DeserializeObject<Point>(obj.RootElement.ToString()); | ||
return (Point?) geoJsonObject; | ||
Comment on lines
+445
to
+447
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading data is not directly related to how data is ingested. Shouldn't the marshalling work when enabling GeoJSON by |
||
} | ||
Comment on lines
+438
to
+448
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dito: Manual procedures are currently needed when working with .NET's native GeoJSON types. Here, the code uses |
||
} | ||
|
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,9 @@ | |
using System.Linq; | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
using GeoJSON.Net.Geometry; | ||
using Npgsql; | ||
using NpgsqlTypes; | ||
using Xunit; | ||
|
||
namespace demo.tests | ||
|
@@ -21,12 +23,7 @@ public DatabaseFixture() | |
{ | ||
CRATEDB_DSN = $"Host=localhost;Port=5432;Username=crate;Password=;Database=testdrive"; | ||
} | ||
Console.WriteLine($"Connecting to {CRATEDB_DSN}\n"); | ||
|
||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(CRATEDB_DSN); | ||
dataSourceBuilder.EnableDynamicJson(); | ||
using var dataSource = dataSourceBuilder.Build(); | ||
Db = dataSource.OpenConnection(); | ||
Db = DemoProgram.GetConnection(CRATEDB_DSN); | ||
} | ||
|
||
public void Dispose() | ||
|
@@ -129,11 +126,11 @@ public async Task TestAllTypesNativeExample() | |
// Assert.Equal(new Dictionary<string, string>{{"foo", "bar"}}, row["object"]); | ||
|
||
// Geospatial types | ||
// TODO: Unlock native data types? | ||
// GEO_POINT and GEO_SHAPE types can be marshalled back and forth using STRING. | ||
// GEO_POINT is using a tuple format, GEO_SHAPE is using the GeoJSON format. | ||
// Assert.Equal(new List<double>{85.43, 66.23}, row["geopoint"]); // TODO | ||
Assert.Equal("(85.42999997735023,66.22999997343868)", row["geopoint"].ToString()); // FIXME | ||
// While `GEO_POINT` is transparently marshalled as `NpgsqlPoint`, | ||
// `GEO_SHAPE` is communicated as scalar `string` type, using the GeoJSON format. | ||
// TODO: Possibly support transparently converging `GEO_SHAPE` to one of | ||
// `NpgsqlLSeg`, `NpgsqlBox`, `NpgsqlPath`, `NpgsqlPolygon`, `NpgsqlCircle`. | ||
Assert.Equal(new NpgsqlPoint(85.42999997735023, 66.22999997343868), row["geopoint"]); | ||
Assert.Equal("""{"coordinates":[[[5.0,5.0],[5.0,10.0],[10.0,10.0],[10.0,5.0],[5.0,5.0]]],"type":"Polygon"}""", row["geoshape"]); | ||
|
||
// Vector type | ||
|
@@ -215,5 +212,21 @@ public async Task TestArrayPocoExample() | |
|
||
} | ||
|
||
[Fact] | ||
public async Task TestGeoJsonTypesExample() | ||
{ | ||
var conn = fixture.Db; | ||
|
||
// Provision data. | ||
var task = new DatabaseWorkloadsTypes(conn).GeoJsonTypesExample(); | ||
var point = await task.WaitAsync(TimeSpan.FromSeconds(0.5)); | ||
|
||
// Validate the outcome. | ||
var coords = new Point(new Position(85.43, 66.23)).Coordinates; | ||
Assert.Equal(coords.Latitude, point?.Coordinates.Latitude); | ||
Assert.Equal(coords.Longitude, point?.Coordinates.Longitude); | ||
Comment on lines
+224
to
+227
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't directly compare GeoJSON object instances. Need to run the comparison on the individual coordinates inside. 🤷 |
||
|
||
} | ||
|
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this sending
GEOJson
(so a map encoded in JSON) to CrateDB?If so, this could work when explicitly casting the insert value to a map/object:
or
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your suggestions, here and below. I will investigate them and report back.