Skip to content
Felix Obermaier edited this page Jan 25, 2022 · 13 revisions

Getting Started

This introduction is also available as jupyter notebook Binder

Setting up the environment

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

Creating geometries

NetTopologySuite provides 7 Geometry classes. Geometries are made up of Coordinates which are combined in CoordinateSequences.

  • Point
    A geometry made up of a single coordinate.
  • LineString
    A geometry made up of a sequence of successive points. A LinearRing is a special case of a closed LineString
  • Polygon
    A geometry made up of a shell (aka exterior ring) and possibly holes (aka InteriorRing). The shell and each hole is a LinearRing 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);

Persistance of geometries

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.

Well-known-text

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);

Other projects/packages

There are seperate packages for reading and writing NetTopologySuite's Geometry classes:

Spatial predicates

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.

Equals

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*/)
Disjoint

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)
Intersects

Evaluates to true if a geometry spatially intersects another geometry.

bool intersects = geom.Intersects(otherGeom)
Touches

Evaluates to true if a geometry spatially touches another geometry.

bool touches = geom.Touches(otherGeom);
Crosses

Evaluates to true if a geometry spatially crosses another geometry.

bool crosses = geom.Crosses(otherGeom);
Within

Evaluates to true if a geometry is spatially within another geometry.

bool within = geom.Within(otherGeom);
Contains

Evaluates to true if a geometry spatially contains another geometry.

bool contains = geom.Contains(otherGeom);
Overlaps

Evaluates to true if a geometry spatially overlaps another geometry.

bool overlaps = geom.Overlaps(otherGeom);
Relate

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
    }
}

Spatial operations

The following examples assume we have a WKTReader like

var rdr = new NetTopologySuite.IO.WKTReader();
Intersection
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);
Difference
// 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);
SymmetricDifference
// Should be MULTILINESTRING((5 15, 15 25), (35 20, 40 21))
var lnSymDiff = ln1.SymmetricDifference(ln2);
Union
// 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();
Buffer
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);
ConvexHull
// Should be POLYGON ((10 10, 30 10, 40 20, 40 40, 20 40, 10 30, 10 10))
var ch = polyUnion.ConvexHull();
PointOnSurface
// Should be POINT (25 25)
var pos = polyUnion.PointOnSurface;

Invalid Geometries

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);