-
Notifications
You must be signed in to change notification settings - Fork 314
GettingStarted
This introduction is also available as jupyter notebook
To make using NTS a pleasant experience you 1st should set up the environment to your needs.
This is done by setting NtsGeometryServices.Instance
to an instance configured to your needs.
NetTopologySuite.NtsGeometryServices.Instance = new NetTopologySuite.NtsGeometryServices(
// default CoordinateSequenceFactory
NetTopologySuite.Geometries.Implementation.CoordinateArraySequenceFactory.Instance,
// default precision model
new NetTopologySuite.Geometries.PrecisionModel(1000d),
// default SRID
4326,
/********************************************************************
* Note: the following arguments are only valid for NTS >= v2.2
********************************************************************/
// Geometry overlay operation function set to use (Legacy or NG)
NetTopologySuite.Geometries.GeometryOverlay.NG,
// Coordinate equality comparer to use (CoordinateEqualityComparer or PerOrdinateEqualityComparer)
new NetTopologySuite.Geometries.CoordinateEqualityComparer());
This is the most flexible constructor you can use, there are convenient constructors with less options.
Note: If you skip this step, a pre-configured instance will be used with the following values:
Property | Value |
---|---|
DefaultCoordinateSequenceFactory |
NetTopologySuite.Geometries.Implementation.CoordinateArraySequenceFactory.Instance |
DefaultPrecisionModel |
NetTopologySuite.Geometries.PrecisionModels.Floating |
DefaultSRID |
-1 |
GeometryOverlay |
NetTopologySuite.Geometries.GeometryOverlay.Legacy |
CoordinateEqualityComparer |
NetTopologySuite.Geometries.CoordinateEqualityComparer |
NetTopologySuite
provides 7 Geometry
classes. Geometries are made up of Coordinate
s which are combined in CoordinateSequence
s.
-
Point
A geometry made up of a single coordinate. -
LineString
A geometry made up of a sequence of successive points. ALinearRing
is a special case of a closedLineString
-
Polygon
A geometry made up of a shell (aka exterior ring) and possibly holes (aka InteriorRing). The shell and each hole is aLinearRing
geometry. -
MultiPoint
A geometry made up of multiple points -
MultiLineString
A geometry made up of multiple linestrings -
MultiPolygon
A geometry made up of multiple polygons -
GeometryCollection
A geometry made up of multiple single-geometry items.
While each Geometry class has a public constructor the usage is not encouraged. You should use a GeometryFactory
instead.
You can optain a geometry factory by requesting one from NetTopologySuite.NtsGeometryServices.Instance
:
// Create a geometry factory with the spatial reference id 4326
var gf = NetTopologySuite.NtsGeometryServices.Instance.CreateGeometryFactory(4326);
You can now use this factory to create your geometries:
// Get a geometry factor from configured NtsGemetryServices.
// Differing from NtsGeometryServices.DefaultSRID we want one with SRID=4326
var gf = NetTopologySuite.NtsGeometryServices.Instance.CreateGeometryFactory(4326);
// Create a point at Aurich (lat=53.4837, long=7.5404)
var pntAUR = gf.CreatePoint(new NetTopologySuite.Geometries.Coordinate(7.5404, 53.4837));
// Create a point at Emden (lat=53.3646, long=7.1559)
var pntLER = gf.CreatePoint(new NetTopologySuite.Geometries.Coordinate(7.1559, 53.3646));
// Create a point at Leer (lat=53.2476, long=7.4550)
var pntEMD = gf.CreatePoint(new NetTopologySuite.Geometries.Coordinate(7.4550, 53.2476));
// Create a linestring from Aurich to Emden
var lnsAURToEMD = gf.CreateLineString(new [] {
new NetTopologySuite.Geometries.Coordinate(7.5404, 53.4837),
new NetTopologySuite.Geometries.Coordinate(7.1559, 53.3646)
});
var lnsAURtoLER = gf.CreateLineString(new[] {
new NetTopologySuite.Geometries.Coordinate(7.5404, 53.4837),
new NetTopologySuite.Geometries.Coordinate(7.4550, 53.2476)
});
// Create a polygon from Aurich over Emden, Leer to Aurich
var ply1 = gf.CreatePolygon(new[] {
new NetTopologySuite.Geometries.Coordinate(7.5404, 53.4837),
new NetTopologySuite.Geometries.Coordinate(7.1559, 53.3646),
new NetTopologySuite.Geometries.Coordinate(7.4550, 53.2476),
new NetTopologySuite.Geometries.Coordinate(7.5404, 53.4837),
});
// Alternativly you can build this polygon by building a LinearRing first.
// A LinearRing requires 4 coordinates and 1st and last coordinate must be equal!
var lnr = gf.CreateLinearRing(new[] {
new NetTopologySuite.Geometries.Coordinate(7.5404, 53.4837),
new NetTopologySuite.Geometries.Coordinate(7.1559, 53.3646),
new NetTopologySuite.Geometries.Coordinate(7.4550, 53.2476),
new NetTopologySuite.Geometries.Coordinate(7.5404, 53.4837),
});
var ply2 = gf.CreatePolygon(lnr);
// Multi Geometries are build by passing arrays of geometries
var mpnt = gf.CreateMultiPoint(new[] {pntAUR, pntLER, pntEMD});
var mlns = gf.CreateMultiLineString(new[] { lnsAURToEMD, lnsAURtoLER });
var mpoly = gf.CreateMultiPolygon(new[] { ply1 });
// A geometry collection
var gc = gf.CreateGeometryCollection(
new NetTopologySuite.Geometries.Geometry[]
{ pntAUR, lnsAURToEMD, pntEMD, ply2, pntLER, lnsAURtoLER });
Instead of using Coordinate
class you can also use one of its derivates:
-
CoordinateZ
for 3D coordinates. -
CoordinateM
for 2D coordinates with an additional measure value. -
CoordinateZM
for 3D coordinates with an additional measure value.
Or you can create a CoordinateSequence
and build the single-instance geometries using that:
// Create coordinate sequence
var cs = gf.CoordinateSequenceFactory.Create(1, NetTopologySuite.Geometries.Ordinates.XYM);
cs.SetX(0, 7.5404);
cs.SetY(0, 53.4837);
cs.SetM(0, 5432);
var tpAUR2 = gf.CreatePoint(cs);
While all Geometry
classes are marked with SerializeableAttribute
using this form of serialization is
not the preferred way of dealing with persistance.
Out of the box the NetTopologySuite
package provides
reader and writer classes for the Well-known-text
(WKT) and Well-known-binary
(WKB) format.
const string wkt = "POINT M(7.5404 53.4837 5432)";
var rdr = new NetTopologySuite.IO.WKTReader();
var ptAUR = rdr.Read(wkt);
var wrt = new NetTopologySuite.IO.WKTWriter();
wrt.OutputOrdinates = NetTopologySuite.Geometries.Ordinates.AllOrdinates;
string wktOut = wrt.Write(ptAUR);
// or plainly for strictly 2D geometries!
wktOut = ptGeom.AsText();
wktOut = ptGeom.ToString();
byte[] wkb = NetTopologySuite.IO.WKBReader.HexToBytes(
"01B90B00E0E8640000295C8FC2F5281E40CBA145B6F3BD4A40000000000000F8FF000000000038B540");
var rdr = new NetTopologySuite.IO.WKBReader {
HandleOrdinates = NetTopologySuite.Geometries.Ordinates.AllOrdinates,
HandleSRID = true };
var ptAUR = rdr.Read(wkb);
var wrt = new NetTopologySuite.IO.WKBWriter(NetTopologySuite.IO.ByteOrder.LittleEndian,
/* emit SRID */ true, /* emit Z */ true, /* emit M */ true);
byte[] wktOut = wrt.Write(ptAUR);
There are seperate packages for reading and writing NetTopologySuite's Geometry
classes:
-
NetTopologySuite.IO.GeoJSON
This project actually offers two packages to read and write geometries each using a different support library for serializing JSON.-
NetTopologySuite.IO.GeoJSON
usingNewtonsoft.Json
-
NetTopologySuite.IO.GeoJSON4STJ
usingSystem.Text.Json
-
NetTopologySuite.IO.SqlServerBytes
NetTopologySuite.IO.ShapeFile
NetTopologySuite.IO.PostGis
NetTopologySuite.IO.TinyWKB
NetTopologySuite's Geometry
classes support the following predicates as defined in the OpenGIS® Implementation Standard for Geographic information - Simple feature access - Part 1: Common architecture.
Evaluates to true
if a geometry is spatially equal to another geometry.
// As defined in SFA-Common
bool equalSfs = geom.EqualsTopologically(otherGeom)
// As required for .Net. 'EqualsExact' is called by overload of object.Equals(object obj)
bool equalNet = geom.EqualsExact(otherGeom/*, tolerance*/)
Evaluates to true
if a geometry is spatially disjoint to another geometry. This equivalent to negating the return value of an intersection test.
bool disjoint = geom.Disjoint(otherGeom)
Evaluates to true
if a geometry spatially intersects another geometry.
bool intersects = geom.Intersects(otherGeom)
Evaluates to true
if a geometry spatially touches another geometry.
bool touches = geom.Touches(otherGeom);
Evaluates to true
if a geometry spatially crosses another geometry.
bool crosses = geom.Crosses(otherGeom);
Evaluates to true
if a geometry is spatially within another geometry.
bool within = geom.Within(otherGeom);
Evaluates to true
if a geometry spatially contains another geometry.
bool contains = geom.Contains(otherGeom);
Evaluates to true
if a geometry spatially overlaps another geometry.
bool overlaps = geom.Overlaps(otherGeom);
Evaluates the relationship between a geometry and another geometry
(see DE-9IM).
An overload of this function tests if an
assumed intersection matrix correctly describes the relationship.
var im = geom.Relate(otherGeom);
bool relate = geom.Relate(otherGeom, im.ToString());
It is worth noting that for 1:M predicate checks there are utility classes in NetTopologySuite.Geometries.Prepared
namespace.
var prepGeom = NetTopologySuite.Geometries.Prepared.PreparedGeometryFactory.Prepare(geom);
foreach (var geomItem in geometries)
{
// instead of 'Intersects' there are also
// the other normal predicates except 'Relate',
// plus 'ContainsProperly'
if (prepGeom.Intersects(geomItem))
{
// do sth. with geomItem
}
}
The following examples assume we have a WKTReader
like
var rdr = new NetTopologySuite.IO.WKTReader();
const string wktPoly1 = "POLYGON ((10 10, 10 30, 30 30, 30 10, 10 10))";
const string wktPoly2 = "POLYGON ((20 20, 20 40, 40 40, 40 20, 20 20))";
var poly1 = rdr.Read(wktPoly1);
var poly2 = rdr.Read(wktPoly2);
// Should be POLYGON ((20 30, 30 30, 30 20, 20 20, 20 30))
var polyInt = poly1.Intersection(poly2);
// Should be POLYGON ((10 10, 10 30, 20 30, 20 20, 30 20, 30 10, 10 10))
var polyDiff = poly1.Difference(poly2);
const string wktLine1 = "LINESTRING (5 15, 15 25, 35 20)";
const string wktLine2 = "LINESTRING (15 25, 35 20, 40 21)";
var ln1 = rdr.Read(wktLine1);
var ln2 = rdr.Read(wktLine2);
// Should be LINESTRING(15 25, 35 20)
var lnDiff = ln1.Difference(ln2);
// Should be MULTILINESTRING((5 15, 15 25), (35 20, 40 21))
var lnSymDiff = ln1.SymmetricDifference(ln2);
// Should be POLYGON ((10 10, 10 30, 20 30, 20 40, 40 40, 40 20, 30 20, 30 10, 10 10))
var polyUnion = poly1.Union(poly2);
// Should be GEOMETRYCOLLECTION (
// LINESTRING (5 15, 10 20),
// POLYGON ((10 10, 10 20, 10 30, 20 30, 20 40, 40 40, 40 21, 40 20, 35 20, 30 20, 30 10, 10 10)))
var allUnion = poly1.Factory.CreateGeometryCollection(
new NetTopologySuite.Geometries.Geometry[]
{
poly1, poly2, ln1, ln2
}).Union();
const string wktPoint = "POINT (15 15)";
var pt = rdr.Read(wktPoint);
// Create a buffer around a point with distance of 2d
var ptBuffer = pt.Buffer(2);
// Should be POLYGON ((10 10, 30 10, 40 20, 40 40, 20 40, 10 30, 10 10))
var ch = polyUnion.ConvexHull();
// Should be POINT (25 25)
var pos = polyUnion.PointOnSurface;
Sometimes you will meet invalid geometries (Geometry.IsValid == false
).
These will cause issues while further processing them. Starting with NTS v2.4 you can use NetTopologySuite.Geometries.Utilities.GeometryFixer
to fix these invalidities.
if (!geom.IsValid)
geom = NetTopologySuite.Geometries.Utilities.GeometryFixer.Fix(geom);
In previous versions one option to fix these geometries was to use the Buffer0 trick:
if (!geom.IsValid)
geom = geom.Buffer(0);