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

4

9

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();
}
Matronymic answered 23/7, 2019 at 22:25 Comment(2)
Can you confirm that you're using nuget.org/packages/NetTopologySuite.IO.GeoJSON, and let us know the specific version?Subserve
I'm using the most current version of NetTopologySuiteMatronymic
I
4

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.

Invincible answered 29/7, 2019 at 14:10 Comment(1)
so what about serializing ?Macilroy
S
15

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:

Demo fiddle here.

Subserve answered 26/7, 2019 at 22:39 Comment(4)
This might work, but it seems like a more complicated solution than what I'm currently doing, so I'm not going to award any points.Matronymic
The point is that it is more correct, because you will be using the converters designed for the classes you are deserializing. Also, once those extension methods are written, they can be reused with any data model that contains NetTopologySuite.Geometries content.Subserve
But if you look at my current solution I don't have to do any of that...so correct != simplicity and I'm not going to need it anywhere other than to seed my data to my database on initial build. It's a one time use case if you look at how I need it and where I'm using it. void SeedUsers()Matronymic
Just as an fyi, the two links above the demo line 404.Pleomorphism
I
4

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.

Invincible answered 29/7, 2019 at 14:10 Comment(1)
so what about serializing ?Macilroy
R
3

Because the NetTopologiySuite's Point object doesn't contain a parameterless constructor you can't easily map the JSON during deserailization.

But, you can easily create your own Location object and then map the values to the NetTopologySuite's Point object within your loop.

First, define a new Location object...

public class Location
{
    public double Longitude { get; set; }
    public double Latitude { get; set; }
    public int SRID { get; set; }
}

Next, update your JSON's location line with the below line to define the Location object:

"Location": {"Longitude":-122.4194155, "Latitude":37.7749295, "SRID":4326},

Full JSON:

[{
        "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": {"Longitude":-122.4194155, "Latitude":37.7749295, "SRID":4326},
        "Photos": [{
            "url": "https://randomuser.me/api/portraits/women/3.jpg",
            "isMain": true,
            "description": "Non deserunt labore sunt ex laboris et adipisicing ullamco officia minim."
        }]
    }]

Next, update your User object to use your new Location object and set the JsonIgnore attribute on the NetTopologySuite's Point object:

public class User : IdentityUser<int>
{
    // member data here

    public Location Location { get; set; }

    [JsonIgnore]
    public Point LocationPoint { get; set; } // has lat/lng data points
}

Finally, update your foreach loop to map the data...

foreach (var user in users)
{
    user.Photos.SingleOrDefault().IsApproved = true;
    user.LocationPoint = new Point(user.Location.Longitude, user.Location.Latitude) {SRID = user.Location.SRID};
    _userManager.CreateAsync(user, "password").Wait();
    _userManager.AddToRoleAsync(user, "Member").Wait();
}

All said and done, you may want to reconsider using the NetTopologySuite's Point object directly within your User object, and instead use your own Location object. Then you would transpose to the NetTopologySuite's Point object closer to the code that actually uses the Point. This really depends on your application though.

Rudolph answered 26/7, 2019 at 17:48 Comment(2)
if you look at my post, you'll see that your solution is very similar to what I'm currently doing and not what I want.Matronymic
@user1186050 fair enough. But your post asked how to get the data from the JSON file, which my example does and yours does not.Rudolph
G
1

A bit late to the party but here is my take on this: you can easily easily make the Point compatible with your current Json Serializer settings.

[DataContract]
public class GeoLocation : NetTopologySuite.Geometries.Point
{
    const int GoogleMapSRID = 4326 ;

    public GeoLocation(double latitude, double longitude)
        : base(x: longitude, y: latitude) =>
          base.SRID = GoogleMapsSRID;

    [DataMember]
    public double Longitude => base.X;

    [DataMember]
    public double Latitude => base.Y;
}

The DataContract and DataMember are key here:

new GeoLocation(42.9074, -78.7911).ToJson() => {"longitude":42.9074,"latitude":-78.7911}
Gastrotrich answered 9/3, 2020 at 4:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.