-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
SqlServer: Support spatial data via NTS #13115
Conversation
Doh, compile error on net461. Looks like I need to change my awesome use of |
Check.NotNull(serviceCollection, nameof(serviceCollection)); | ||
|
||
// TODO: Use EF infrastructure? | ||
serviceCollection.TryAddSingleton(NtsGeometryServices.Instance); |
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.
@ajcvickers Worth creating a new services builder?
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.
Probably not worth it for just this service.
// TODO: Remove when fixed in NTS | ||
if (_sequenceFactory is PackedCoordinateSequenceFactory packedFactory) | ||
{ | ||
ordinates = OrdinatesUtility.DimensionToOrdinates(packedFactory.Dimension); |
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.
Submitted NetTopologySuite/NetTopologySuite#255
protected override string GenerateNonNullSqlLiteral(object value) | ||
{ | ||
// TODO: Can we avoid the conversion in the first place? Should we just inline the BLOB? | ||
var geometry = _reader.Read(((SqlBytes)value).Value); |
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.
@ajcvickers Have we considered adding a hook before the value gets converted?
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.
I just had a few notes / suggestions as I was reading through the NTS parts of this. I've never used EF itself, so I obviously can't comment much on those bits, heh.
} | ||
catch (EndOfStreamException) | ||
{ | ||
throw new FormatException(); |
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.
Might it make more sense to provide a more specific message, and/or move this into Geography.ReadFrom
?
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.
Definitely belongs in Geography.ReadFrom
. The default message isn't very useful, is it:
One of the identified items was in an invalid format.
var geometries = new Queue<(IGeometry, int)>(); | ||
geometries.Enqueue((geometry, -1)); | ||
|
||
// TODO: For geography (ellipsoidal) data, set IsLargerThanAHemisphere. Can this be represented in NTS? |
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.
Can this be represented in NTS?
Not directly / separately, but I think the logic would be:
bool IsLargerThanAHemisphere(IGeometry someGeometry)
{
return someGeometry.EnvelopeInternal.Width > 180 || someGeometry.EnvelopeInternal.Height > 90;
}
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.
then again, this would often do the completely wrong thing if the geometry wraps around the antimeridian (e.g., Alaska including Aleutians), so it would need to be a bit more sophisticated than this sample code.
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! I also wanted to dig into why SQL Server even needs this. I think it's related to how the external rings of geography
polygons are handled...
var pointOffset = geography.Points.Count; | ||
var pointsAdded = false; | ||
|
||
foreach (var coordinate in g.Coordinates) |
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.
PERF: I'd typically do this using a GeoAPI.Geometries.ICoordinateSequenceFilter
implementation (Done
= false, GeometryChanged
= false), to avoid lots of extra allocations and copying all the data around.
Then this would be something like:
var pointOffset = geography.Points.Count;
var filter = new MyGeographyPopulatingFilter(geography, _emitZ, _emitM);
g.Apply(filter);
if (filter.PointsAdded)
{
geography.Figures.Add(
new Figure
{
FigureAttribute = figureAttribute,
PointOffset = pointOffset,
});
}
return filter.PointsAdded;
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! There's a lot of unnecessary allocating here with the enumerators too. We should do some profiling before we RTM.
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.
Filed #13144
var geometry = _reader.Read(((SqlBytes)value).Value); | ||
var srid = geometry.SRID; | ||
|
||
// TODO: This won't emit M |
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.
note: NetTopologySuite/NetTopologySuite#156 is the reference number
/// <summary> | ||
/// Represents plugin member translators. | ||
/// </summary> | ||
public interface IMemberTranslatorPlugin |
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.
@divega We should probably call out these plugin mechanisms explicitly in the preview2 blog post because they are generally useful outside of just spatial.
@@ -58,7 +58,19 @@ public abstract class RelationalTypeMappingSource : TypeMappingSourceBase, IRela | |||
/// </summary> | |||
/// <param name="mappingInfo"> The mapping info to use to create the mapping. </param> | |||
/// <returns> The type mapping, or <c>null</c> if none could be found. </returns> | |||
protected abstract RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo); | |||
protected virtual RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo) |
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.
Add to the provider log that base.FindMapping should be called?
|
||
namespace NetTopologySuite.IO.Serialization | ||
{ | ||
internal class Figure |
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.
Internal because these move to NTS before RTM?
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.
Yep.
public virtual bool HandleSRID | ||
{ | ||
get => true; | ||
set { } |
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.
Add API doc that setting is a no-op and why. Also for RepairRings.
/// <summary> | ||
/// Gets or sets whether the SpatialReference ID must be handled. | ||
/// </summary> | ||
public virtual bool HandleSRID |
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.
Likewise in this class
|
||
using System.Runtime.CompilerServices; | ||
|
||
[assembly: InternalsVisibleTo("Microsoft.EntityFrameworkCore.SqlServer.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] |
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.
Temporary?
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.
Probably not needed. I'll remove.
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.
Very nice!
Looks like |
There's still a lot to do (e.g. support mapping to GEOGRAPHY), bit it's at a point where we can start gathering feedback while we continue iterating on it. To get started, install the `Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite` package, call `.UseSqlServer(..., x => x.UseNetTopologySuite())`, and add `Geometry` properties to your model. Part of #1100
There's still a lot to do (e.g. support mapping to GEOGRAPHY), bit it's at a point where we can start gathering feedback while we continue iterating on it.
To get started, install the
Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite
package, call.UseSqlServer(..., x => x.UseNetTopologySuite())
, and addIGeometry
properties to your model.Part of #1100