7
votes

Entity Framework Core 3.1 avec NetTopologySuite.Geometries.Point: SqlException: la valeur fournie n'est pas une instance valide de la géographie du type de données

J'ai un modèle qui ressemble à ceci:

var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=TestDb;Trusted_Connection=True;MultipleActiveResultSets=true",
    x => x.UseNetTopologySuite());

var db = new ApplicationDbContext(optionsBuilder.Options);

Code de test pour l'ajout d'un point:

Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite

J'utilise les NuGets suivants, version 3.1.0

var testFacility = new Facility();
testFacility.Location = new NetTopologySuite.Geometries.Point(13.003725d, 55.604870d) { SRID = 3857 };

//Other values tested with the same error error

//testFacility.Location = new NetTopologySuite.Geometries.Point(13.003725d, 55.604870d);

//testFacility.Location = new NetTopologySuite.Geometries.Point(55.604870d, 13.003725d);

//var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 3857);
//var currentLocation = geometryFactory.CreatePoint(new Coordinate(13.003725d, 55.604870d));
//testFacility.Location = currentLocation;

db.Facilities.Add(testFacility);
//Exception on Save
db.SaveChanges();

L'exception que j'obtiens lors de l'enregistrement est la suivante:

SqlException: le flux de protocole d'appel de procédure distante (RPC) de flux de données tabulaires entrantes (TDS) est incorrect. Paramètre 7 ("@ p6"): la valeur fournie n'est pas une instance valide du type de données geography. Vérifiez les données source pour les valeurs non valides. Les données de type numérique avec une échelle supérieure à la précision sont un exemple de valeur non valide.

Selon toute la documentation, il devrait être X pour la longitude et Y pour la latitude, donc je ne pense pas que ce soit un problème. J'ai essayé d'inverser les coordonnées au cas où mais j'ai eu la même erreur que celle que vous pouvez voir dans les exemples que j'ai essayés.

https://docs.microsoft.com/en-us/ef/core/modeling/spatial

Lat = Y Long = X

https://gis.stackexchange.com/a/68856/71364

Je ne trouve rien d'évident qui semble faux. Optionsbuilder est configuré, la table est créée avec une geography type de données qui a très bien fonctionné avec DbGeography pour Entity Framework 6.

public class Facility
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public NetTopologySuite.Geometries.Point Location { get; set; }
}

Il n'y a pas de cas spécifiques à gérer pour un Point unique non plus ce que je peux voir dans la documentation du serveur SQL.

https://docs.microsoft.com/en-us/ef/core/modeling/spatial#sql-server

Les coordonnées que j'enregistre proviennent de Google Maps et donc EPSG 3857 est utilisé.

https://gis.stackexchange.com/questions/48949/epsg-3857-or-4326-for-googlemaps-openstreetmap-and-leaflet

Qu'est-ce que je rate?


2 commentaires

Mes pouvoirs psychiques me disent que NET Topology Suite mappe sa classe Point sur le type de geometry MSSQL, pas sur la geography . Par conséquent, en essayant de conserver l'enregistrement, NTS sérialise une geometry tandis que MSSQL attend une geography et tout explose.


@IanKemp Je pense qu'ils auront peut-être besoin d'un réglage alors. ;) C'était le SRID non présent dans SQL Server sys.spatial_reference_systems


3 Réponses :


11
votes

TLDR

Le SRID n'est pas présent dans SQL Server sys.spatial_reference_systems

entrez la description de l'image ici

Changez pour celui qui existe comme 4326 et cela fonctionnera:

entrez la description de l'image ici

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using NetTopologySuite;
using NetTopologySuite.Geometries;
using ProjNet.CoordinateSystems;
using ProjNet.CoordinateSystems.Transformations;
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace TestConsoleAppEFGeo
{
    public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
    {
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=TestApp;Trusted_Connection=True;MultipleActiveResultSets=true",
                x => x.UseNetTopologySuite());

            return new ApplicationDbContext(optionsBuilder.Options);
        }
    }

    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        public virtual DbSet<Facility> Facilities { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }
    }

    public class Facility
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int Id { get; set; }

        public NetTopologySuite.Geometries.Point Location { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var applicationDbContextFactory = new ApplicationDbContextFactory();
            var db = applicationDbContextFactory.CreateDbContext(null);

            var x = 13.003725d;
            var y = 55.604870d;
            var srid = 4326;

            if (!db.Facilities.AnyAsync(x => x.Id == 1).Result)
            {
                var testFacility = new Facility();
                var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid);
                var currentLocation = geometryFactory.CreatePoint(new NetTopologySuite.Geometries.Coordinate(x, y));
                testFacility.Id = 1;
                testFacility.Location = currentLocation;

                var testFacility2 = new Facility();
                testFacility2.Id = 2;
                testFacility2.Location = new Point(x, y) { SRID = srid };
                db.Facilities.Add(testFacility);
                db.Facilities.Add(testFacility2);

                //Will throw an exception
                //var testFacility3 = new Facility();
                //testFacility3.Id = 3;
                //testFacility3.Location = new Point(1447568.0454157612d, 7480155.2276327936d) { SRID = 3857 };
                //db.Facilities.Add(testFacility3);

                db.SaveChanges();
            }

            var facility1 = db.Facilities.FirstAsync(x => x.Id == 1).Result;
            var facility2 = db.Facilities.FirstAsync(x => x.Id == 2).Result;

            if(facility1.Location == facility2.Location)
            {
                Console.WriteLine("facility1.Location is equal to facility2.Location");
            }
            else
            {
                Console.WriteLine("facility1.Location is NOT equal to facility2.Location");
            }

            //Test conversion
            //Show coordinate: http://epsg.io/map#srs=4326&x=13.003725&y=55.604870&z=14&layer=streets
            //Conversion: http://epsg.io/transform#s_srs=4326&t_srs=3857&x=13.0037250&y=55.6048700
            //Google Maps - https://www.google.se/maps shows EPSG:4326 when viewing a location
            //https://epsg.io/3857 - Google Maps API is EPSG:3857 however
            //Example: https://developers.google.com/maps/documentation/javascript/markers

            var epsg3857ProjectedCoordinateSystem = ProjectedCoordinateSystem.WebMercator;
            var epsg4326GeographicCoordinateSystem = GeographicCoordinateSystem.WGS84;

            var coordinateTransformationFactory = new CoordinateTransformationFactory();
            var coordinateTransformation = coordinateTransformationFactory.CreateFromCoordinateSystems(epsg4326GeographicCoordinateSystem, epsg3857ProjectedCoordinateSystem);

            var epsg4326Coordinate = new GeoAPI.Geometries.Coordinate(facility1.Location.Coordinate.X, facility1.Location.Coordinate.Y);

            var epsg3857Coordinate = coordinateTransformation.MathTransform.Transform(epsg4326Coordinate);

        }
    }
}

Longue réponse:

L'API Google Maps utilise EPSG 3857 mais l'application Web Google Maps utilise EPSG 4326

https://developers.google.com/maps/documentation/javascript/markers

https://www.google.com/maps/@55.604933,13.003662,14z

entrez la description de l'image ici

Par conséquent, un point de l'application Web Google Maps doit être créé et enregistré comme ceci:

var x = 13.003725d;
var y = 55.604870d;

var epsg3857ProjectedCoordinateSystem = ProjNet.CoordinateSystems.ProjectedCoordinateSystem.WebMercator;
var epsg4326GeographicCoordinateSystem = ProjNet.CoordinateSystems.GeographicCoordinateSystem.WGS84;

var coordinateTransformationFactory = new ProjNet.CoordinateSystems.Transformations.CoordinateTransformationFactory();
var coordinateTransformation = coordinateTransformationFactory.CreateFromCoordinateSystems(epsg4326GeographicCoordinateSystem, epsg3857ProjectedCoordinateSystem);

var epsg4326Coordinate = new GeoAPI.Geometries.Coordinate(x, y);

var epsg3857Coordinate = coordinateTransformation.MathTransform.Transform(epsg4326Coordinate);

Il était cependant un peu délicat de projeter les coordonnées EPSG 4326 coordonnées EPSG 3857 . Microsoft recommande d'utiliser ProjNet4GeoAPI , j'ai donc décidé de l'utiliser.

https://docs.microsoft.com/en-us/ef/core/modeling/spatial#srid-ignored-during-client-operations

J'ai vérifié que cela fonctionne ici:

http://epsg.io/transform#s_srs=4326&t_srs=3857&x=13.003725&y=55.604870

Exemple de conversion:

var testFacility = new Facility();
testFacility.Location = new NetTopologySuite.Geometries.Point(13.003725d, 55.604870d) { SRID = 4326 };
db.Facilities.Add(testFacility);
db.SaveChanges();

Exemple de programme complet:

Pour le faire fonctionner:

  • Installer NuGets
    • Les NuGets suivants sont à la version 3.1:
      • Microsoft.EntityFrameworkCore
      • Microsoft.EntityFrameworkCore.SqlServer
      • Microsoft.EntityFrameworkCore.Tools
      • Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite
    • ProjNET4GeoAPI
  • Add-Migration InitialCreate
  • Update-Database

Code:

select *
from sys.spatial_reference_systems
where spatial_reference_id = '4326'

Plus d'infos ici:

https://github.com/dotnet/efcore/issues/19416


1 commentaires

Pourquoi le correctif ne peut-il pas être quelque chose comme: INSERT INTO sys.spatial_reference_systems (spatial_reference_id, author_name, allowed_spatial_reference_id, well_known_text, unit_of_measure, unit_conversion_factor) VALUES (3857, 'EPSG', 3857, 'GEOGCS'] , 1)?



0
votes

Assurez-vous que votre Lat et Long sont dans les bonnes positions et que vous fournissez un SRID valide.


0 commentaires

0
votes

C'est probablement déjà dans 4326 , jours heureux, facile à stocker, sql devrait vous permettre de stocker ceci (une API peut utiliser 3857 mais fournir la latitude / longitude d'un emplacement en degrés et non en mètres et en fait, vous avez déjà reçu la latitude / longitude en 4326 )


En supposant que vous obtenez des lat / lon dans SRID=3857 et que vous voulez essayer de le stocker de cette façon:

Vérifiez que vous disposez d'une version d'un SRID équivalent à 3857 qui fonctionnera dans votre base de données

testFacility.Location = new NetTopologySuite.Geometries.Point(1.536553, 49.823796) 
                             { SRID = 4326 };

Par exemple, si vous avez 900913, essayez de l'utiliser sur un insert lat / lon sans conversion si vous l'avez, je base cette hypothèse sur la comparaison des propriétés des "codes alternatifs" hyperliens à EPSG: 3857

Je n'ai aucune idée si cela fonctionnera, et ce n'est absolument pas mon domaine de connaissance.

En supposant que vous n'obteniez aucune ligne SQL à l'époque, vous devrez convertir 3857 en 4326 pour les stocker dans votre base de données ...


Comment convertir 3857 en 4326 pour pouvoir le stocker:

Installez ProjNet4GeoAPI via NuGet et utilisez le code suivant:

// setup
var epsg3857 = ProjectedCoordinateSystem.WebMercator;
var epsg4326 = GeographicCoordinateSystem.WGS84;
var convertTo4326 = new CoordinateTransformationFactory()
                        .CreateFromCoordinateSystems(epsg3857, epsg4326);

// input 6415816.17/171048.38 (Brussels lat/lon in meters SRID 3857)
//       N.B. method called needs the values as lon/lat (x/y), not lat/lon 
var coordIn3857 = new GeoAPI.Geometries.Coordinate(171048.38, 6415816.17);

var coordIn4326 = convertTo4326.MathTransform.Transform(coordIn3857);
// output 49.82379612579344/1.5365537407788388 (Brussels lat/lon in degrees SRID 4326)

...

using GeoAPI.Geometries;
using ProjNet.CoordinateSystems;
using ProjNet.CoordinateSystems.Transformations;

allez maintenant enregistrer ça dans votre DB

SELECT * FROM sys.spatial_reference_systems 
WHERE authorized_spatial_reference_id 
IN('3857', '900913', '3587', '54004', '41001', '102113', '102100', '3785')

Pour convertir dans l'autre sens et utiliser 3857 partir des valeurs 4326 stockées, il est assez facile de comprendre ou de voir la réponse d'Ogglas


0 commentaires