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
Pointsur le type degeometryMSSQL, pas sur lageography. Par conséquent, en essayant de conserver l'enregistrement, NTS sérialise unegeometrytandis que MSSQL attend unegeographyet 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