Reputation: 151
I'm trying to map a Multipolygon from a GeoJSON FeatureCollection I have to a property which is a NetTopologySuite type of Multipolygon.
This is what I got:
GeoJSON:
{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"bbox":[13.55188823869398,44.440081209521175,16.6289966237125,46.16059787999315],"type":"MultiPolygon","coordinates":[[[[15.395583981629768,44.57162377024533],[15.387003350545871,44.481720735232564],[15.261789625068943,44.48813408824978],[15.270172550335417,44.578048939581144],[15.395583981629768,44.57162377024533]]],[[[15.896964543940912,44.544415530182626],[16.022240457648902,44.53723684127118],[16.14748751000305,44.52990760944508],[16.137724258293254,44.440081209521175],[16.012673860640266,44.44739698760816],[15.88759478603406,44.45456249478511],[15.76248761526664,44.46157767625901],[15.771660353342368,44.55144362101705],[15.896964543940912,44.544415530182626]]],[[[15.152955209351642,44.674242834298695],[15.027294842687972,44.6803775578638],[15.035349165740053,44.770301745657044],[15.161209210808025,44.764155656145604],[15.152955209351642,44.674242834298695]]],[[[15.395583981629768,44.57162377024533],[15.40420062719178,44.661519892738035],[15.52978449007481,44.65493177334668],[15.538636545464684,44.74480883793866],[15.664392732134285,44.73805721601009],[15.655341665136223,44.64819262251561],[15.646328471445097,44.55832105973176],[15.520969484947091,44.56504779340633],[15.395583981629768,44.57162377024533]]],[[[15.906373586831855,44.63426143624865],[15.780871560861883,44.641302492390295],[15.790121463337991,44.731154339362874],[15.915822144904903,44.7241002616354],[15.906373586831855,44.63426143624865]]],[[[14.909464652174844,44.77629632941221],[14.783556270565919,44.78213936065267],[14.791243042374749,44.8720788835874],[14.917352248635115,44.86622499380096],[14.909464652174844,44.77629632941221]]],[[[15.40420062719178,44.661519892738035],[15.278590669072724,44.667956929679484],[15.28704418791397,44.75785810878928],[15.295533315163146,44.84775252708752],[15.421542808951754,44.84129159985158],[15.412853498605203,44.751409152650304],[15.40420062719178,44.661519892738035]]],[[[15.295533315163146,44.84775252708752],[15.169497988826626,44.854061767894706],[15.17782174856998,44.943961219958226],[15.304058260811797,44.937640234683336],[15.295533315163146,44.84775252708752]]],[[[15.67348189503577,44.82791488944938],[15.547525868962715,44.83467903673759],[15.55645268017813,44.92454241922587],[15.682609378238029,44.91776569199236],[15.67348189503577,44.82791488944938]]],[[[15.574419653120414,45.10424893283504],[15.447831530393547,45.11089856282971],[15.456668762059818,45.20075425647369],[15.583460263041763,45.19409216255446],[15.574419653120414,45.10424893283504]]],[[[15.084394984513631,45.309708772973096],[15.076133963305915,45.21982378925574],[14.94923765800067,45.2258745480123],[14.941215654803312,45.13597182321009],[14.814498911953551,45.1418585743132],[14.822316698924032,45.23177234906947],[14.695371707066238,45.237517145249605],[14.703017502529459,45.32743542422996],[14.710695871844338,45.4173474925388],[14.8380521975077,45.41158102170872],[14.830167727940616,45.321679814539486],[14.957293763601468,45.315770914953696],[14.965384175175874,45.40566097472601],[15.092691175810298,45.39958739941671],[15.084394984513631,45.309708772973096]]],[16.19120538066795,46.06345399526351],[16.062454717796655,46.07086579545412]]]]},"properties":{"code":"1167","maptype":"Distribution","category":"Species","isocode":"HR","refgrid":"EEA 10x10km","sensitive":"non-sensitive"}}],"fileName":"Test"}
My model:
public class Species
{
public int Id { get; set; }
public string Name { get; set; }
//property to which I want to map the MultiPolygon feature from GeoJSON. This is a NetToplogySuite type.
public MultiPolygon Range { get; set; }
}
I tried using NetTopologySuite and GeoJSON.Net libraries to do the GeoJSON conversion and mapping but I didn't get far. I'm receiving GeoJSON as a string in my controller:
using NetTopologySuite.IO;
using NetTopologySuite.Geometries;
using GeoJSON.Net.Feature;
using GeoJSON.Net;
public void GeoJson(string json)
{
Species newSpecies = new Species();
var reader = new GeoJsonReader();
var featureCollection = reader.Read<FeatureCollection>(json);
if (featureCollection == null)
{
return;
}
// loop through all the parsed features
for (int featureIndex = 0;
featureIndex < featureCollection.Features.Count;
featureIndex++)
{
// get json feature
var jsonFeature = featureCollection.Features[featureIndex];
Geometry geom = null;
// get geometry type to create appropriate geometry
switch (jsonFeature.Geometry.Type)
{
case GeoJSONObjectType.Point:
break;
case GeoJSONObjectType.MultiPoint:
break;
case GeoJSONObjectType.LineString:
break;
case GeoJSONObjectType.MultiLineString:
break;
case GeoJSONObjectType.Polygon:
break;
case GeoJSONObjectType.MultiPolygon:
{
//this is where I want to convert GeoJSON MultiPolygon to a NetToplogySuite MultiPolygon but I'm don't know how. Basically I want to map the newSpecies.Range to the MultiPolygon jsonFeature.
}
break;
case GeoJSONObjectType.GeometryCollection:
break;
case GeoJSONObjectType.Feature:
break;
case GeoJSONObjectType.FeatureCollection:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
Any ideas?
Thank you!
Upvotes: 3
Views: 6458
Reputation: 56
After reading almost every page that exists on the internet, I refactored your answer after (what I believe) is a copy and paste mistake. I could never find out what you used firstLevelCoordinateList for, except adding to.
So, I refactored your code. This version may contain traces of GeoJSON.Net, but I since tried to remove traces of it and base it entirely on NetTopologySuite. However, another solution later emerged...
using GeoJSON.Net;
using GeoJSON.Net.Contrib.EntityFramework;
using GeoJSON.Net.Feature;
using NetTopologySuite.Geometries;
using NetTopologySuite.Geometries.Implementation;
using RekylService.ExternalSync.DataPortal.Imports.Interfaces;
using System.Collections.Generic;
using System.Linq;
using LineString = GeoJSON.Net.Geometry.LineString;
using MultiLineString = GeoJSON.Net.Geometry.MultiLineString;
using MultiPoint = GeoJSON.Net.Geometry.MultiPoint;
using MultiPolygon = GeoJSON.Net.Geometry.MultiPolygon;
using Point = GeoJSON.Net.Geometry.Point;
using NTS = NetTopologySuite;
namespace Parser
{
public class GeoShapeJsonParser
{
public ICollection<Geometry> ParseGeoShapes(string data)
{
var reader = new NTS.IO.GeoJsonReader();
var featureCollection = reader.Read<FeatureCollection>(data);
if (featureCollection == null)
return null;
var geoFactory = NTS.NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var shapes = new List<Geometry>();
foreach (var shape in featureCollection.Features)
{
if (shape.Type == GeoJSONObjectType.MultiPolygon)
{
var multipolygon = shape.Geometry as MultiPolygon;
var ringList = new List<NTS.Geometries.LinearRing>();
var polygonList = new List<NTS.Geometries.Polygon>();
foreach (var xpolygon in multipolygon.Coordinates) //I had a name conflict, hence xpolygon instead of polygon ;)
{
foreach (var lineString in xpolygon.Coordinates) //the first lineString is the shell, and the ones following are holes
{
var linearRingCoordinates = new List<NTS.Geometries.Coordinate>();
foreach (var coordinate in lineString.Coordinates)
{
var coordinates = new NTS.Geometries.Coordinate(coordinate.Longitude, coordinate.Latitude);
linearRingCoordinates.Add(coordinates);
}
var ring = geoFactory.CreateLinearRing(linearRingCoordinates.ToArray());
ringList.Add(ring);
}
var polygon = geoFactory.CreatePolygon(ringList.First(), ringList.Skip(1).ToArray()); //this takes the first ring as outer shell and the rest as holes
polygonList.Add(polygon);
}
shapes.Add(geoFactory.CreateMultiPolygon(polygonList.ToArray()));
}
}
return shapes;
This solution also considers holes in the polygons. Here comes the kick, though (100% NetTopologySuite now, and all shapes):
public class GeoShapeJsonParser
{
public ICollection<Geometry> ParseGeoShapes(string data)
{
var reader = new GeoJsonReader();
var featureCollection = reader.Read<FeatureCollection>(data);
var shapes = new List<Geometry>();
foreach (var feature in featureCollection)
shapes.AddRange(Extract(feature));
}
public IEnumerable<Geometry> Extract(IFeature feature)
{
var extract = new List<Geometry>();
new GeometryExtracter<Point>(extract).Filter(feature.Geometry);
new GeometryExtracter<MultiPoint>(extract).Filter(feature.Geometry);
new GeometryExtracter<LineString>(extract).Filter(feature.Geometry);
new GeometryExtracter<MultiLineString>(extract).Filter(feature.Geometry);
new GeometryExtracter<LinearRing>(extract).Filter(feature.Geometry);
new GeometryExtracter<Polygon>(extract).Filter(feature.Geometry);
new GeometryExtracter<MultiPolygon>(extract).Filter(feature.Geometry);
return extract;
}
This is absolutely the best, shortest and fastest way to parse out all geoshapes. I'm amazed at the lack of documentation around this topic.
In order for me to save this to SQL server, I'm making each shape into a WKT before sending them away by using "the extracted geometry object".ToText()
.
In the repository I'm assigning them to the field as DbGeography.FromText("the extracted geometry object")
.
Upvotes: 4
Reputation: 151
Here's a solution I managed to write after a few days trial and error. Basically a geoJSON goes in as a string, gets converted to JSON using Parse method from Newtonsoft.Json and then to a FetureCollection using GeoJsonReader from GeoJSON.NET. I then loop through all the parsed features and create appropriate geometries. In this case I was only interested in the Multipolygon feature which I converted to a Multipolygon type from NetTopologySuite.
using NetTopologySuite.IO;
using NetTopologySuite.Geometries;
using GeoJSON.Net.Feature;
using GeoJSON.Net;
[HttpPost]
public void GeoJson(string json) {
var test = json;
var item = JObject.Parse(json);
Species newSpecies = new Species();
var reader = new GeoJsonReader();
var featureCollection = reader.Read < FeatureCollection > (json);
if (featureCollection == null) {
return;
}
// loop through all the parsed features
for (int featureIndex = 0; featureIndex < featureCollection.Features.Count; featureIndex++) {
// get json feature
var jsonFeature = featureCollection.Features[featureIndex];
// get geometry type to create appropriate geometry
switch (jsonFeature.Geometry.Type) {
case GeoJSONObjectType.Point:
break;
case GeoJSONObjectType.MultiPoint:
break;
case GeoJSONObjectType.LineString:
break;
case GeoJSONObjectType.MultiLineString:
break;
case GeoJSONObjectType.Polygon:
break;
case GeoJSONObjectType.MultiPolygon:
{
var multipoly = jsonFeature.Geometry as GeoJSON.Net.Geometry.MultiPolygon;
var polygonList = new List < Polygon > ();
foreach(var firstLevel in multipoly.Coordinates) {
var firstLevelCoordinateList = new List < List < Coordinate >> ();
foreach(var secondLevel in firstLevel.Coordinates) {
var secondLevelCoordinateList = new List < Coordinate > ();
foreach(var thirdLevel in secondLevel.Coordinates) {
var coordinates = new Coordinate(thirdLevel.Longitude, thirdLevel.Latitude);
secondLevelCoordinateList.Add(coordinates);
}
var coordArr = new Coordinate[secondLevelCoordinateList.Count];
for (int i = 0; i < secondLevelCoordinateList.Count; i++) {
coordArr[i] = secondLevelCoordinateList[i];
}
var poly = new GeometryFactory().CreatePolygon(coordArr);
polygonList.Add(poly);
firstLevelCoordinateList.Add(secondLevelCoordinateList);
}
}
var polyArr = new Polygon[polygonList.Count];
for (int i = 0; i < polygonList.Count; i++) {
polyArr[i] = polygonList[i];
}
//NetTopologySuite Multipolygon property
newSpecies.Range = new GeometryFactory().CreateMultiPolygon(polyArr);
}
break;
case GeoJSONObjectType.GeometryCollection:
break;
case GeoJSONObjectType.Feature:
break;
case GeoJSONObjectType.FeatureCollection:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
Upvotes: 2