DHI.Spatial.Projections — Internal Developer Guide¶
A tiny, focused utility for reprojecting DHI.Spatial geometries between coordinate reference systems (CRS) using DotSpatial.Projections.
- Namespace:
DHI.Spatial.Projections - Primary type:
CoordinateTransformer(static) - Depends on:
DHI.Spatial(our geometry model),DotSpatial.Projections
Projection is done vertex-by-vertex. Topology is not guaranteed to remain valid after transformation (e.g., self-intersections may appear).
What it does¶
- Reprojects individual geometries:
Point,MultiPoint,LineString,MultiLineString,Polygon,MultiPolygon. - Reprojects collections of geometries.
- Accepts CRS as either:
- EPSG code strings (e.g.,
"4326","3857"), or - DHI
CoordinateReferenceSystemobjects withType = "name"and a"name"property like"EPSG:4326".
- EPSG code strings (e.g.,
Public API¶
// Project a single geometry by EPSG codes
public static IGeometry Project(IGeometry geometry, string inSrs, string outSrs);
// Project a collection by EPSG codes
public static IEnumerable<IGeometry> Project(IEnumerable<IGeometry> geometries, string inSrs, string outSrs);
// Project a single geometry using its own CRS to a target CRS
public static IGeometry Project(IGeometry geometry, CoordinateReferenceSystem outCrs);
// Project a collection of geometries using each geometry’s own CRS to a target CRS
public static IEnumerable<IGeometry> Project(IEnumerable<IGeometry> geometries, CoordinateReferenceSystem outCrs);
Behavior & expectations¶
- Axis order: DotSpatial uses X = longitude, Y = latitude for geographic CRS such as EPSG:4326. Provide coordinates accordingly.
- Z handling: If a coordinate has
Z:- We pass
Zthrough to DotSpatial (as an array). Many projections do not alter Z, so expect Z to be preserved. If originalZisnull, we pass0and do not emit a Z in the result for that vertex.
- We pass
- CRS on result:
Project(geometry, inSrs, outSrs): does not setgeometry.CRSon the result (caller may set it).Project(geometry, CoordinateReferenceSystem outCrs): does setresult.CRS = outCrs.
- Collections: Collection overloads simply map each geometry through the corresponding single-geometry method.
Usage examples¶
1) EPSG -> EPSG (single geometry)¶
using DHI.Spatial;
using DHI.Spatial.Projections;
var p4326 = new Point(new Position(12.0, 55.0)); // lon, lat
var p3857 = CoordinateTransformer.Project(p4326, "4326", "3857");
// Optionally record the CRS on the result yourself:
p3857.CRS = new CoordinateReferenceSystem { Type = "name" };
p3857.CRS.Properties["name"] = "EPSG:3857";
2) Geometry with CRS -> target CRS¶
var line = new LineString();
line.Coordinates.Add(new Position(12.0,55.0));
line.Coordinates.Add(new Position(12.5,55.1));
line.CRS = new CoordinateReferenceSystem { Type = "name" };
line.CRS.Properties["name"] = "EPSG:4326";
var outCrs = new CoordinateReferenceSystem { Type = "name" };
outCrs.Properties["name"] = "EPSG:3857";
var line3857 = CoordinateTransformer.Project(line, outCrs);
// line3857.CRS == outCrs
3) Batch reproject¶
IEnumerable<IGeometry> geoms = GetGeometries(); // no CRS on geometries
var outGeoms = CoordinateTransformer.Project(geoms, "4326", "3857");
or when each geometry has a CRS:
var outCrs = new CoordinateReferenceSystem { Type = "name" };
outCrs.Properties["name"] = "EPSG:3857";
var outGeoms = CoordinateTransformer.Project(geoms, outCrs);
Supported geometry types¶
- ✓
Point - ✓
MultiPoint - ✓
LineString - ✓
MultiLineString - ✓
Polygon(outer ring + holes, if present) - ✓
MultiPolygon(with holes) - ✖
GeometryCollection(project its members individually)
CRS parsing rules¶
When using the CRS object overloads:
- We require
CoordinateReferenceSystem.Type == "name". - We require a
"name"property containing something like"EPSG:4326"or a URN ending with the EPSG code. - The code uses only the last colon-separated token, e.g.:
"EPSG:4326"->"4326""urn:ogc:def:crs:EPSG::4326"->"4326"
If these conditions aren’t met, we throw ArgumentException.
Errors & exceptions¶
- If
inSrsoroutSrsis not an integer ->ArgumentException(“invalid format”). - If EPSG code is not registered with DotSpatial ->
ArgumentException(“not registered”). - If a geometry lacks
CRSin the CRS-object overload ->ArgumentException. - Unknown geometry types return
null(and skip thethrow). Prefer to guard your inputs.
Topology & quality notes¶
- No topology guarantees. We reproject vertices and rebuild shapes:
- Polygons might become invalid (self-intersection) after projection.
- Ring orientation is preserved as-is; we don’t enforce closure or orientation corrections.
- Densification: If projecting long segments in geographic CRS (e.g., near the dateline), consider densifying before projecting.
- Precision: Reprojection is performed in double precision; round-off can still alter shape characteristics slightly.
Performance characteristics¶
- Each projection call flattens coordinates to XY/Z arrays and invokes
Reproject.ReprojectPointsonce per geometry. This is efficient for medium/large geometries. - Collection overloads recompute
ProjectionInfoper geometry (because they call the single-geometry method). For very large batches with the same SRS pair, consider extracting the code to reuseProjectionInfo(potential improvement). - Memory: Arrays are sized to the total vertex count of the geometry. For very large
MultiPolygons, this can be substantial.
Best practices¶
- Axis order: Always pass (lon, lat) for 4326. If your source is (lat, lon), swap before projecting.
- CRS alias “900913”: Historically used for Web Mercator; EPSG:3857 is the canonical code. Depending on DotSpatial version,
"900913"may or may not resolve. Prefer"3857". - Z values: We pass Z through. Most horizontal CRS transforms won’t change Z; if you rely on true vertical transformations, handle Z separately.
- Result CRS: If you use the
stringoverloads, remember to setCRSon the result yourself (or switch to theCoordinateReferenceSystemoverload).
Internals (how it’s built)¶
- For each supported geometry kind we:
- Flatten coordinates into
double[] xyanddouble[] z. - Call
Reproject.ReprojectPoints(xy, z, pStart, pEnd, 0, count). - Reconstruct a new geometry, preserving the presence/absence of Z on each vertex.
- Flatten coordinates into
This design keeps the code straightforward and maps cleanly onto DotSpatial’s API.