-
Notifications
You must be signed in to change notification settings - Fork 46
Using NTS.IO.GeoJSON4STJ with ASP.NET Core MVC
Joe Amenta edited this page Nov 24, 2022
·
10 revisions
The package: https://www.nuget.org/packages/NetTopologySuite.IO.GeoJSON4STJ
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
// this constructor is overloaded. see other overloads for options.
var geoJsonConverterFactory = new GeoJsonConverterFactory();
options.JsonSerializerOptions.Converters.Add(geoJsonConverterFactory);
});
// nothing to do with NTS.IO.GeoJSON4STJ specifically, but a recommended
// best-practice is to inject this instead of using the global variable:
builder.Services.AddSingleton(NtsGeometryServices.Instance);
public void ConfigureServices(IServiceCollection services)
{
services
.AddControllers()
.AddJsonOptions(options =>
{
// this constructor is overloaded. see other overloads for options.
var geoJsonConverterFactory = new GeoJsonConverterFactory();
options.JsonSerializerOptions.Converters.Add(geoJsonConverterFactory);
});
// nothing to do with NTS.IO.GeoJSON4STJ specifically, but a recommended
// best-practice is to inject this instead of using the global variable:
services.AddSingleton(NtsGeometryServices.Instance);
}
According to some users' experiences with the Newtonsoft.Json version, you may need to tweak your AddControllers
call like so:
.AddControllers(options =>
{
// Prevent the following exception: 'This method does not support GeometryCollection arguments'
// See: https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/issues/585
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Point)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Coordinate)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(LineString)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(MultiLineString)));
})
Here is a modified version of the controller that gets created when you run dotnet new webapi
in .NET SDK 7.0.100, meant to demonstrate how to accept and return GeoJSON objects, including some slightly fancy features.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NetTopologySuite;
using NetTopologySuite.Features;
using NetTopologySuite.Geometries;
using NetTopologySuite.IO.Converters;
namespace YourProjectNamespace.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
private readonly IOptions<JsonOptions> _jsonOptions;
private readonly NtsGeometryServices _geometryServices;
public WeatherForecastController(ILogger<WeatherForecastController> logger, IOptions<JsonOptions> jsonOptions, NtsGeometryServices geometryServices)
{
_logger = logger;
// if you need to use TryGetJsonObjectPropertyValue, then you will
// want to inject this in order for it to work correctly:
_jsonOptions = jsonOptions;
// in Startup.ConfigureServices:
// services.AddSingleton(NtsGeometryServices.Instance);
_geometryServices = geometryServices;
}
// sample: emit GeoJSON objects as output
[HttpGet]
public FeatureCollection Get()
{
var geometryFactory = _geometryServices.CreateGeometryFactory();
var result = new FeatureCollection();
for (int index = 0; index < 5; index++)
{
result.Add(new Feature
{
Geometry = geometryFactory.CreatePoint(new Coordinate(Random.Shared.NextDouble() * 340 - 170, Random.Shared.NextDouble() * 160 - 80)),
Attributes = new AttributesTable
{
// by default, an attribute with this property name
// will be written as the Feature's "id", instead of
// storing it in the "properties" of the Feature.
// you can change the name of the "special" property by
// using a different GeoJsonConverterFactory constructor
// (remember to update other code that uses it, though)
{ GeoJsonConverterFactory.DefaultIdPropertyName, Guid.NewGuid() },
// for anything that you want to be able to parse back,
// make sure to nest it at least one level.
{
"forecast", new AttributesTable
{
{ "date", DateOnly.FromDateTime(DateTime.Now.AddDays(index)) },
{ "temperatureC", Random.Shared.Next(-20, 55) },
{ "summary", Summaries[Random.Shared.Next(Summaries.Length)] },
}
},
},
});
}
return result;
}
// sample: accept GeoJSON objects as input
// POST data is identical to the GET data.
[HttpPost]
public IEnumerable<WeatherForecastFeatureDetail> Post(FeatureCollection forecastFeatures)
{
foreach (var forecastFeature in forecastFeatures)
{
if (!(forecastFeature.GetOptionalId(GeoJsonConverterFactory.DefaultIdPropertyName) is string forecastIdString && Guid.TryParse(forecastIdString, out Guid id)))
{
throw new ArgumentException("missing 'id' on a feature, or it isn't a GUID");
}
if (forecastFeature.Geometry is not Point point)
{
throw new ArgumentException("missing 'geometry' on a feature, or it isn't a Point");
}
// this library always deserializes attributes to this concrete type
if (forecastFeature.Attributes is not JsonElementAttributesTable forecastAttributes)
{
throw new ArgumentException("missing 'properties' on a feature");
}
if (!forecastAttributes.TryGetJsonObjectPropertyValue("forecast", _jsonOptions.Value.JsonSerializerOptions, out WeatherForecast forecast))
{
throw new ArgumentException("'forecast' property is not a WeatherForecast");
}
yield return new WeatherForecastFeatureDetail
{
Id = id,
Location = point,
Forecast = forecast,
};
}
}
public sealed record WeatherForecastFeatureDetail
{
public required Guid Id { get; init; }
public required Point Location { get; init; }
public required WeatherForecast Forecast { get; init; }
}
}