Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to seed NetTopologySuite.Geometries.Point data from a Json file in ASP.Net core

I want to seed "Location" data for my user object from my seed file

The c# object, where Point is a NetTopologySuite.Geometries.Point is part of my user object

  public class User: IdentityUser<int> {
      // member data here
      public Point Location { get; set; } // has lat/lng data points
  }

I seed data to my db on startup by doing something like this

public void SeedUsers()
{
    if (!_userManager.Users.Any())
    {
        var userData = System.IO.File.ReadAllText("Data/UserSeedData.json");
        var users = JsonConvert.DeserializeObject<List<User>>(userData);

        var roles = new List<Role>
        {
            new Role{Name = "Member"},
            new Role{Name = "Admin"},
            new Role{Name = "Moderator"},
            new Role{Name = "VIP"},
        };

        foreach (var role in roles)
        {
            _roleManager.CreateAsync(role).Wait();
        }

        foreach (var user in users)
        {
            user.Photos.SingleOrDefault().IsApproved = true;
            _userManager.CreateAsync(user, "password").Wait();
            _userManager.AddToRoleAsync(user, "Member").Wait();
        }
     }
 }

with a json file "UserSeedData.json" of json arrays like this and I want to be able to stick some kind of 'Location' data in there that is representative of lng/lat data points.

{
  "Email": "[email protected]",
  "Username": "Lola",
  "Gender": "female",
  "DateOfBirth": "1994-02-21",
  "Password": "password",
  "Created": "2017-08-02",
  "LastActive": "2017-08-02",
  "Introduction": "blah blah blah",
  "LookingFor": "blah blah blah",
  "City": "San Francisco",
  "Country": "United States",
  "Longitude": -122.431297,
  "Latitude": 37.773972,
  "Location": // something here!!!
  "Photos": [{
    "url": "https://randomuser.me/api/portraits/women/3.jpg",
    "isMain": true,
    "description": "Non deserunt labore sunt ex laboris et adipisicing ullamco officia minim."
  }]
}

Now I know in my seed method I could do something like this, but I'm looking for a way to include it in my .json file, so I can use different data points

foreach (var user in users)
{
    user.Photos.SingleOrDefault().IsApproved = true;
    user.Location = new Point(-122.4194155, 37.7749295) { SRID = 4326 };
    _userManager.CreateAsync(user, "password").Wait();
    _userManager.AddToRoleAsync(user, "Member").Wait();
}
like image 467
user1186050 Avatar asked Jul 23 '19 22:07

user1186050


2 Answers

NetTopologySuite has a separate nuget, NetTopologySuite.IO.GeoJSON, for serializing NetTopologySuite types from and to JSON using Json.NET. It includes converters for geometry objects such as Point. If you add this nuget to your project you will be able to add geometry entities such as Point to your data model and (de)serialize the model directly.

To do this, first add NetTopologySuite.IO.GeoJSON to your project.

Then add the following extension method:

public static partial class JsonExtensions
{
    public static T LoadFromFileWithGeoJson<T>(string path, JsonSerializerSettings settings = null)
    {
        var serializer = NetTopologySuite.IO.GeoJsonSerializer.CreateDefault(settings);
        serializer.CheckAdditionalContent = true;
        using (var textReader = new StreamReader(path))
        using (var jsonReader = new JsonTextReader(textReader))
        {
            return serializer.Deserialize<T>(jsonReader);
        }
    }
}

And add a Location property to your User model as in your question:

public class User : IdentityUser<int>
{
    public Point Location { get; set; }

    // Remainder unchanged.
    // ...
}

Now, the JSON format for a Point looks like:

{"type":"Point","coordinates":[-122.431297,37.773972]}

So edit your JSON file to look like:

[
  {
    "Location": {
      "type": "Point",
      "coordinates": [
        -122.431297,
        37.773972
      ]
    },
    // Remainder unchanged

Having done all this, you will be able to deserialize your JSON file quite simply as follows:

var users = JsonExtensions.LoadFromFileWithGeoJson<List<User>>("Data/UserSeedData.json");

Notes:

  • NetTopologySuite.IO.GeoJSON requires Newtonsoft.Json version 9.0.1 or greater. If you are using a later version you may need to add a bindingRedirect to avoid build warnings.

  • See HowTo use [NetTopologySuite.IO.GeoJSON] with ASP.NET Core for additional information on integrating this package in your project.

  • The SRID seems not to be saved as part of the point's JSON. Instead it is set by the IGeometryFactory used when deserializing the Point, which by default is new GeometryFactory(new PrecisionModel(), 4326);.

    If you need control over this you can construct a JsonSerializer using a specific factory by using GeoJsonSerializer.Create(IGeometryFactory factory).

Demo fiddle here.

like image 135
dbc Avatar answered Sep 19 '22 17:09

dbc


You could subclass NetTopologySuite.Geometries.Point and add a [JsonConstructor] to parse your json file. It should be a straightforward substitution for the rest of your code.

public class MyPoint : Point
{
    [JsonConstructor]
    public MyPoint(double latitude, double longitude, int srid)
        :base(new GeoAPI.Geometries.Coordinate(longitude, latitude))
    {
        SRID = srid;
    }
}

Note that latitude = y and longitude = x so the order is reversed.

Swap MyPoint for Point in your User class

public class User: IdentityUser<int> {
  // member data here
  public MyPoint Location { get; set; }
}

And it should work with your json as is.

like image 45
ste-fu Avatar answered Sep 16 '22 17:09

ste-fu