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é.
Qu'est-ce que je rate?
3 Réponses :
TLDR
Le SRID n'est pas présent dans SQL Server sys.spatial_reference_systems
Changez pour celui qui existe comme 4326
et cela fonctionnera:
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
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:
Code:
select * from sys.spatial_reference_systems where spatial_reference_id = '4326'
Plus d'infos ici:
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)?
Assurez-vous que votre Lat et Long sont dans les bonnes positions et que vous fournissez un SRID valide.
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
Mes pouvoirs psychiques me disent que NET Topology Suite mappe sa classe
Point
sur le type degeometry
MSSQL, pas sur lageography
. Par conséquent, en essayant de conserver l'enregistrement, NTS sérialise unegeometry
tandis que MSSQL attend unegeography
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