Skip to content
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

Merged
merged 1 commit into from
Aug 30, 2018
Merged

SqlServer: Support spatial data via NTS #13115

merged 1 commit into from
Aug 30, 2018

Conversation

bricelam
Copy link
Contributor

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 IGeometry properties to your model.

Part of #1100

@bricelam
Copy link
Contributor Author

Doh, compile error on net461. Looks like I need to change my awesome use of Span<T>.

Check.NotNull(serviceCollection, nameof(serviceCollection));

// TODO: Use EF infrastructure?
serviceCollection.TryAddSingleton(NtsGeometryServices.Instance);
Copy link
Contributor Author

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?

Copy link
Member

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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Contributor Author

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?

Copy link

@airbreather airbreather left a 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();

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?

Copy link
Contributor Author

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?

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;
}

Copy link

@airbreather airbreather Aug 28, 2018

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.

Copy link
Contributor Author

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)
Copy link

@airbreather airbreather Aug 28, 2018

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;

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

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
Copy link
Member

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)
Copy link
Member

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
Copy link
Member

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?

Copy link
Contributor Author

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 { }
Copy link
Member

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
Copy link
Member

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")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temporary?

Copy link
Contributor Author

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.

Copy link
Member

@ajcvickers ajcvickers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!

@bricelam
Copy link
Contributor Author

Looks like GeometryValueComparer isn't getting used. Need to dig into that before merging...

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants