Skip to content

ProjectionConverter Guide

Namespace: ThinkGeo.Core
Assembly: ThinkGeo.Core
Availability: All ThinkGeo products (WPF, WinForms, Blazor, MAUI, Web API, etc.)


Part 1: Understanding Map Projections and Coordinate Systems

Before diving into the ThinkGeo API, it is worth understanding the problem that ProjectionConverter solves — because it is a problem that trips up nearly every developer who works with geospatial data for the first time. The short version: the Earth is a sphere (roughly), and your screen is flat. Getting from one to the other requires mathematics, and different datasets use different mathematics. ProjectionConverter is what reconciles them.


The Earth Is Not Flat

The Earth is an oblate spheroid — slightly flattened at the poles and slightly bulging at the equator. To describe positions on its surface precisely, we use a geographic coordinate system (GCS): a mathematical framework that assigns two angular values to every point.

  • Latitude measures the angle north or south of the equator, ranging from −90° (South Pole) to +90° (North Pole).
  • Longitude measures the angle east or west of the Prime Meridian (which passes through Greenwich, England), ranging from −180° to +180°.

A position expressed as latitude/longitude — such as 33.15°N, 96.83°W for Frisco, Texas — is a position on a three-dimensional curved surface. These values are often called decimal degrees (DD) when written as plain numbers like 33.150083, -96.834516.

Geographic coordinate systems describe where on the globe something is, but they do not tell you how to draw it on a flat surface.


Datums: Which Earth Are We Using?

Before you can even talk about projections, there is a more fundamental question: which mathematical model of the Earth are you using? The Earth is not a perfect sphere, and different geodetic datums model its shape differently.

A datum defines: - The shape of the reference ellipsoid (the mathematical approximation of the Earth's surface) - Where that ellipsoid is anchored relative to the Earth's center of mass

The most widely used datum today is WGS84 (World Geodetic System 1984), which underpins GPS and most modern datasets. Another common datum is NAD83 (North American Datum 1983), used throughout North America. For most practical purposes these two are very close (within a meter), but they are technically distinct.

Older datasets — particularly historical survey data, legacy shapefiles from government agencies, and many state plane coordinate datasets — may be based on NAD27 or other regional datums. Mixing datums without accounting for the difference can introduce errors of tens to hundreds of meters.


Map Projections: Flattening the Globe

A map projection is a mathematical transformation that converts locations on a three-dimensional spheroid into positions on a two-dimensional plane. Every projection does this by stretching, compressing, or otherwise distorting the globe in some way. There is no perfect projection — it is a mathematical impossibility to flatten a sphere without distortion. Different projections trade off different types of distortion depending on what property they preserve.

The four fundamental properties a projection can preserve (but no single projection preserves all four):

  • Shape (conformal): Angles and local shapes are correct. Land masses look right at small scales. Examples: Mercator, Lambert Conformal Conic.
  • Area (equal-area): The relative sizes of regions are correct. A country that covers twice the land area as another will appear twice as large on the map. Examples: Albers Equal Area, Mollweide.
  • Distance (equidistant): Distances from one specific point (or along specific lines) are correct. Examples: Azimuthal Equidistant.
  • Direction (azimuthal): Directions from one specific point are correct. Examples: Gnomonic.

Common Projections and When They Are Used

Mercator / Web Mercator (EPSG:3857)
The Mercator projection was invented in 1569 by Gerardus Mercator for navigation. It preserves angles (conformal), meaning compass bearings appear as straight lines — extremely useful for sailing. The trade-off is severe area distortion at high latitudes: Greenland appears roughly the same size as Africa on a Mercator map, even though Africa is about 14 times larger.

Web Mercator (also called Spherical Mercator, EPSG:3857, or EPSG:900913) is a slight variant that treats the Earth as a perfect sphere rather than an ellipsoid. This makes the math faster and the results close enough for web tile rendering. It is the de-facto standard for web mapping — Google Maps, OpenStreetMap, Bing Maps, and ThinkGeo Cloud Maps all use it. The units are meters (easting and northing), not degrees.

WGS84 Geographic (EPSG:4326)
This is not a projection in the traditional sense — it is a geographic coordinate system that simply uses latitude and longitude in decimal degrees directly as X and Y. It is the native coordinate system of GPS receivers and most raw geospatial datasets. It is often used for data interchange and storage because it is universal, but it is not ideal for accurate area or distance calculations.

State Plane Coordinate System (SPCS)
The United States is divided into over 100 State Plane zones, each covering a small geographic area. Each zone uses either a Lambert Conformal Conic or Transverse Mercator projection optimized to minimize distortion within that zone. State Plane coordinates are expressed in feet (US Survey Feet in older datasets) or meters, and the EPSG codes vary by zone (for example, Texas North Central is EPSG:2276, and Texas Central is EPSG:2277). State Plane data is extremely common in US municipal and county GIS datasets — parcel data, road centerlines, zoning layers, and building footprints are often distributed in State Plane.

UTM (Universal Transverse Mercator)
UTM divides the Earth into 60 north-south zones, each 6° of longitude wide, and projects each zone with a Transverse Mercator projection. Coordinates are in meters. UTM is widely used for scientific and military datasets. US zones range roughly from Zone 10N (Pacific Coast) to Zone 19N (New England).

Albers Equal Area Conic
A conic projection that preserves area accurately across the contiguous United States. Used by the US Census Bureau and the USGS for national-scale thematic maps. Excellent when you need to accurately compare the size of geographic features.

Lambert Conformal Conic
Preserves shape (conformal) over a limited range of latitudes. Used for aeronautical charts, many European national coordinate systems, and several North American projections.


Projected Coordinate Systems (PCS) vs. Geographic Coordinate Systems (GCS)

It is important to distinguish between these two types:

Geographic CRS (GCS) Projected CRS (PCS)
Coordinates Latitude / Longitude (angular) Easting / Northing (linear)
Units Degrees Meters or Feet
Covers Whole globe A specific region
Example WGS84 (EPSG:4326) Web Mercator (EPSG:3857), Texas North Central (EPSG:2276)
Good for Data interchange, GPS Rendering, area/distance calculations

A geographic coordinate system answers "where on the globe?" using angles. A projected coordinate system answers "where on a flat map?" using linear measurements. Most rendering engines — including ThinkGeo's MapView — work in a projected coordinate system (typically Web Mercator) because pixel math requires linear units.


EPSG Codes: The Universal Language of Projections

With hundreds of datums and projections in use worldwide, interoperability requires a shared registry. The EPSG Geodetic Parameter Dataset (named after the European Petroleum Survey Group that originally created it, now maintained by IOGP) is that registry. Every coordinate reference system in the database has a unique integer code called an EPSG code (or SRID — Spatial Reference Identifier).

Some essential codes every GIS developer should know:

EPSG Code Name Notes
4326 WGS84 Geographic GPS, raw lat/lon data, data interchange
3857 Web Mercator (Spherical Mercator) Google Maps, OpenStreetMap, ThinkGeo Cloud tiles
900913 Web Mercator (legacy alias) Older alias for 3857; same projection
2276 NAD83 / Texas North Central State Plane, US Survey Feet
2277 NAD83 / Texas Central State Plane, US Survey Feet
26914 NAD83 / UTM Zone 14N UTM, meters
4269 NAD83 Geographic North American lat/lon
27700 British National Grid OS GB, meters
28992 Amersfoort / RD New Netherlands national grid
3395 WGS84 / World Mercator Ellipsoidal Mercator (not Web Mercator)

You can look up any EPSG code at epsg.io or spatialreference.org. When you receive a shapefile or any other geospatial dataset, the accompanying .prj file (if present) defines the coordinate system in WKT (Well Known Text) format. Tools like QGIS or the ogrinfo command-line tool can tell you the EPSG code for any dataset.


Proj Strings: An Alternative to EPSG

EPSG codes cover the majority of common coordinate systems, but sometimes a CRS is not in the registry — particularly custom or historical projections. In these cases, a Proj string (also called a Proj4 string) can define the projection directly using a series of parameters.

Example — the Albers Equal Area projection centered on the US:

+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-96 +x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs

Each +key=value pair specifies a projection parameter: the projection type (+proj=aea = Albers Equal Area), standard parallels, origin, false easting/northing, datum, and units. ThinkGeo's ProjectionConverter accepts Proj strings as an alternative to EPSG codes, which is covered in the constructor reference below.


Why Projection Mismatches Break Your Map

Every ThinkGeo map has a map unit setting (MapView.MapUnit) that tells the rendering engine what coordinate space to expect. If your map is set to GeographyUnit.Meter (the default for maps backed by Web Mercator tiles), all features drawn on it must have coordinates in meters in the Web Mercator space.

If you add a shapefile whose data is stored in decimal degrees (EPSG:4326) without reprojecting it, ThinkGeo will interpret the latitude/longitude values as meter values. A longitude of -96.8 degrees will be plotted at -96.8 meters from the central meridian — which is basically at the map origin, somewhere in Africa or the Atlantic Ocean. The layer will appear to be either blank or grossly misplaced.

This is the single most common source of "I can't see my layer" bugs in ThinkGeo applications, and the fix is always a ProjectionConverter.


Part 2: The ProjectionConverter Class

ProjectionConverter is the ThinkGeo class that handles coordinate reprojection at runtime. It uses the PROJ library under the hood (the same engine used by QGIS, PostGIS, GDAL, and virtually every other professional GIS tool), meaning you get accurate, standards-compliant transformations for any pair of coordinate systems.

Class Hierarchy

ProjectionConverter
  (no base class in ThinkGeo.Core public API)

ProjectionConverter is assigned to FeatureSource.ProjectionConverter to reproject a layer automatically during rendering and querying. It can also be used standalone to convert individual features or coordinate collections manually.


Constructors

All constructors verified against ThinkGeo HowDoI samples.

// Constructor 1: Two EPSG codes (most common)
// internalEpsg = the projection the data is stored in (the shapefile, database, etc.)
// externalEpsg = the projection the map is rendered in (typically 3857)
var projectionConverter = new ProjectionConverter(2276, 3857);

// Constructor 2: One EPSG + one Proj string
// Use when the target projection is not in the EPSG registry
var projectionConverter = new ProjectionConverter(4326, "+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-96 +datum=NAD83 +units=m +no_defs");

// Constructor 3: Two Proj strings
// Use when both projections need full parameter control
var projectionConverter = new ProjectionConverter(
    "+proj=longlat +datum=WGS84 +no_defs",
    "+proj=sterea +lat_0=52.15 +lon_0=5.38 +k=0.9999079 +x_0=155000 +y_0=463000 +datum=RD83 +units=m +no_defs");

The "internal" vs "external" terminology is one of the most common sources of confusion in ThinkGeo. Think of it this way:

  • Internal projection = the coordinate system the data lives in (inside the file). This is where the data "comes from."
  • External projection = the coordinate system the map renders in (outside the file, in the display world). This is where the data "needs to go."

When you assign a ProjectionConverter to FeatureSource.ProjectionConverter, ThinkGeo calls ConvertToExternalProjection on the raw coordinates read from the file before rendering them, and calls ConvertToInternalProjection when translating map-space coordinates back to the data's native space for queries. You set it once and ThinkGeo handles both directions automatically.


Lifecycle: Open and Close

Like other ThinkGeo objects (Layer, FeatureSource), ProjectionConverter follows an open/close lifecycle. Calling Open() initializes the internal PROJ transformation context, which is a relatively expensive operation and should not be repeated for every coordinate.

When assigned to FeatureSource.ProjectionConverter, ThinkGeo automatically opens and closes the converter along with the feature source — you do not need to manage the lifecycle manually.

When using ProjectionConverter standalone (to manually reproject features), you must call Open() before converting and Close() when done:

var projectionConverter = new ProjectionConverter(4326, 3857);
projectionConverter.Open();
// ... do conversions ...
projectionConverter.Close();

Key Methods

Method Description
Open() Initializes the projection context. Must be called before any conversion method when using standalone.
Close() Releases the projection context.
ConvertToExternalProjection(Feature) Converts a single Feature from internal → external projection.
ConvertToExternalProjection(IEnumerable<Feature>) Converts a collection of Features from internal → external projection.
ConvertToInternalProjection(Feature) Converts a single Feature from external → internal projection.
ConvertToInternalProjection(IEnumerable<Feature>) Converts a collection of Features from external → internal projection.

Key Properties

Property Type Description
InternalProjection Projection The source projection (data's native CRS). Read-only after construction.
ExternalProjection Projection The target projection (display CRS). Read-only after construction.
IsOpen bool Whether the converter is currently open.

Part 3: Usage Patterns

Pattern 1: Reprojecting a Layer for Display (Most Common)

This is the standard ThinkGeo pattern. Assign a ProjectionConverter to the layer's FeatureSource before adding the layer to the map. ThinkGeo will reproject all features automatically during rendering.

using ThinkGeo.Core;

// Shapefile stored in Texas North Central State Plane (EPSG:2276, units = US Survey Feet)
var hotelsLayer = new ShapeFileFeatureLayer(@"./Data/Shapefile/Hotels.shp");

// Map is rendering in Web Mercator (EPSG:3857, units = meters)
// internal = 2276 (the file's native CRS)
// external = 3857 (the map's display CRS)
hotelsLayer.FeatureSource.ProjectionConverter = new ProjectionConverter(2276, 3857);

hotelsLayer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle =
    new PointStyle(PointSymbolType.Circle, 8, GeoBrushes.DarkRed, new GeoPen(GeoBrushes.White, 2));
hotelsLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

var overlay = new LayerOverlay();
overlay.Layers.Add("Hotels", hotelsLayer);
mapView.Overlays.Add(overlay);

Timing: Assign ProjectionConverter before the layer is opened or added to the overlay — it is read when the feature source opens. If you need to change the projection at runtime (e.g., switching map projections interactively), assign the new converter and call .Open() on it explicitly before refreshing the map.


Pattern 2: Reprojecting an Individual Feature

Use this when you need to convert a specific coordinate or feature — for example, converting a GPS-reported location (EPSG:4326) into the map's coordinate space to center the map on it, or to pass it to a spatial query method.

// Feature from a GPS device in decimal degrees (EPSG:4326)
var gpsFeature = new Feature(-96.834516, 33.150083);

// Convert to Spherical Mercator (EPSG:3857) for use on the map
var projectionConverter = new ProjectionConverter(4326, 3857);
projectionConverter.Open();
var mercatorFeature = projectionConverter.ConvertToExternalProjection(gpsFeature);
projectionConverter.Close();

// The mercator feature's coordinates are now in meters
// and can be used directly with the map
mapView.CenterPoint = mercatorFeature.GetShape() as PointShape;

Pattern 3: Reprojecting Multiple Features in Bulk

ConvertToExternalProjection and ConvertToInternalProjection both accept an IEnumerable<Feature> or Collection<Feature>, making batch conversion efficient — the PROJ context is opened once and reused for all features.

// Load features from a decimal-degree shapefile
var dataSource = new ShapeFileFeatureSource(@"./Data/Zoning.shp");
dataSource.Open();
var decimalDegreeFeatures = dataSource.GetAllFeatures(ReturningColumnsType.AllColumns);
dataSource.Close();

// Reproject all features from WGS84 (4326) to Spherical Mercator (3857)
var projectionConverter = new ProjectionConverter(4326, 3857);
projectionConverter.Open();
var mercatorFeatures = projectionConverter.ConvertToExternalProjection(decimalDegreeFeatures);
projectionConverter.Close();

// Add to an InMemoryFeatureLayer for display or spatial queries
var featureLayer = new InMemoryFeatureLayer();
foreach (var feature in mercatorFeatures)
{
    featureLayer.InternalFeatures.Add(feature);
}

Why convert to an InMemoryFeatureLayer? When running topological spatial queries (equality, touches, overlaps, etc.) on features with a ProjectionConverter assigned, floating-point rounding between projections can cause subtle mismatches. Pre-converting to the target projection and working in a single coordinate space avoids this class of bugs. The ThinkGeo HowDoI samples for spatial queries use this pattern explicitly for that reason.


Pattern 4: Converting in Reverse (External → Internal)

ConvertToInternalProjection goes the other direction: from the map's display space back into the data's native space. This is useful when you have a coordinate in the map's space (e.g., from a mouse click or a bounding box in display coordinates) and need to query a data source that lives in a different projection.

// User clicked on the map; e.WorldLocation is in Spherical Mercator (3857)
var clickPointMercator = new Feature(e.WorldLocation);

// The zoning shapefile is in Texas North Central (2276)
// We need to convert the click point to State Plane before querying
var projectionConverter = new ProjectionConverter(3857, 2276);
projectionConverter.Open();
var clickPointStatePlane = projectionConverter.ConvertToInternalProjection(clickPointMercator);
projectionConverter.Close();

// Now query the data source using the State Plane coordinate
var zoningSource = new ShapeFileFeatureSource(@"./Data/Zoning.shp");
zoningSource.Open();
var nearbyFeatures = zoningSource.GetFeaturesNearestTo(
    clickPointStatePlane.GetShape() as PointShape,
    GeographyUnit.Feet, 5,
    new Collection<string>());
zoningSource.Close();

Pattern 5: Switching Projections at Runtime

You can change the projection of a layer dynamically — for example, to let users switch between different world projections interactively. Assign a new ProjectionConverter, call Open() on it, and refresh the map. To revert to the data's native projection (no reprojection), set ProjectionConverter to null.

// Switch to Albers Equal Area Conic projection (via Proj string)
var albersLayer = mapView.FindFeatureLayer("world layer");
albersLayer.FeatureSource.ProjectionConverter = new ProjectionConverter(
    4326,
    "+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-96 +datum=NAD83 +units=m +no_defs");
albersLayer.FeatureSource.ProjectionConverter.Open();
mapView.MapUnit = GeographyUnit.Meter;
await mapView.RefreshAsync();

// Revert to native decimal degrees (no reprojection)
albersLayer.FeatureSource.ProjectionConverter = null;
mapView.MapUnit = GeographyUnit.DecimalDegree;
await mapView.RefreshAsync();

Pattern 6: XAML / MAUI Configuration

In MAUI and some XAML-based ThinkGeo products, ProjectionConverter can be configured declaratively in markup using EPSG codes via the Projection class:

<tgCore:ShapeFileFeatureLayer>
    <tgCore:ShapeFileFeatureLayer.FeatureSource>
        <tgCore:ShapeFileFeatureSource
            ShapePathFilename="{tgMaui:AppDataFolder Path=Data/Shapefile/Subdivisions.shp}">
            <tgCore:ShapeFileFeatureSource.ProjectionConverter>
                <tgCore:ProjectionConverter>
                    <tgCore:ProjectionConverter.InternalProjection>
                        <tgCore:Projection Srid="2276"/>
                    </tgCore:ProjectionConverter.InternalProjection>
                    <tgCore:ProjectionConverter.ExternalProjection>
                        <tgCore:Projection Srid="3857"/>
                    </tgCore:ProjectionConverter.ExternalProjection>
                </tgCore:ProjectionConverter>
            </tgCore:ShapeFileFeatureSource.ProjectionConverter>
        </tgCore:ShapeFileFeatureSource>
    </tgCore:ShapeFileFeatureLayer.FeatureSource>
</tgCore:ShapeFileFeatureLayer>

Part 4: Common Pitfalls and Gotchas

1. Getting Internal and External Backwards

This is the most common mistake. The rule is simple but easy to flip under pressure:

  • Internal = where the data is stored (the file)
  • External = where the map renders (the screen)

If you get them backwards, your features will be transformed in the wrong direction — potentially by thousands of kilometers.

// WRONG: data is in State Plane (2276), map is in Web Mercator (3857)
// This would try to convert map coordinates INTO State Plane and treat them as
// the file's coordinates, which is the opposite of what you want.
hotelsLayer.FeatureSource.ProjectionConverter = new ProjectionConverter(3857, 2276); // ❌

// CORRECT
hotelsLayer.FeatureSource.ProjectionConverter = new ProjectionConverter(2276, 3857); // ✅

A reliable way to remember: new ProjectionConverter(fileEpsg, mapEpsg).

2. Forgetting to Set MapUnit

ProjectionConverter reprojects the coordinate values, but it does not tell the MapView what unit space it is now operating in. If you are adding a layer in Web Mercator (EPSG:3857), the map's unit must be set to GeographyUnit.Meter. If you forget this, spatial calculations and extent management will be wrong.

// Always set the map unit to match the external projection
mapView.MapUnit = GeographyUnit.Meter; // for Web Mercator (3857)
// mapView.MapUnit = GeographyUnit.DecimalDegree; // for geographic (4326)
// mapView.MapUnit = GeographyUnit.Feet; // for State Plane in US Survey Feet

3. Not Assigning the Converter Before Open

ProjectionConverter is read when the FeatureSource opens. If you assign it after the layer has already been opened (or after it was added to a live map overlay), you must close and reopen the layer, or call ProjectionConverter.Open() manually and then refresh the map overlay.

// Safe: assign before adding to map
layer.FeatureSource.ProjectionConverter = new ProjectionConverter(2276, 3857);
overlay.Layers.Add(layer); // layer opens here; converter is picked up automatically

// If changing at runtime after the map is live:
layer.FeatureSource.ProjectionConverter = new ProjectionConverter(2276, 3857);
layer.FeatureSource.ProjectionConverter.Open(); // open it manually
await overlay.RefreshAsync();

4. Mixing Layers with Different Projections Without Converters

In a typical ThinkGeo map, all layers share the same map canvas in Web Mercator (EPSG:3857). If you add ten layers and nine of them have ProjectionConverter correctly set but one does not, the one without it will be misplaced or invisible. Double-check every layer when debugging display issues.

5. Spatial Query Rounding Errors Across Projections

When running precision-sensitive topological queries (equality, touches, etc.) on data that has a ProjectionConverter applied, floating-point rounding during on-the-fly reprojection can cause features that should be exactly coincident to appear slightly misaligned. The workaround is to pre-reproject all features into a single consistent coordinate space before querying, as shown in Pattern 3 above.

6. Unknown Projection (No .prj File)

Shapefiles and other data formats use an accompanying .prj file (for shapefiles) or metadata record to declare their coordinate system. If the .prj is missing or the projection is undocumented, you will need to determine the correct EPSG code through other means:

  • Ask the data provider.
  • Look up the geographic region and expected units — if coordinates are in the hundreds of thousands and the data is in Texas, it is probably State Plane.
  • Load the file in QGIS and use Layer → Properties → Information to inspect it.
  • Check the data source's documentation or metadata on data.gov, the local county GIS portal, etc.

Part 5: Quick Reference — Identifying the Right EPSG

When you receive a new dataset and need to figure out its projection, here is a practical checklist:

  1. Check the .prj file if it exists. Open it in a text editor — it contains the projection in WKT format. Tools like epsg.io have a "find by WKT" feature.
  2. Inspect the coordinate values. Decimal-degree coordinates for the continental US should be in the range (−124 to −67, 25 to 49). Coordinates in the tens of thousands are likely State Plane in feet. Coordinates in the hundreds of thousands to millions are likely UTM or State Plane in meters.
  3. Read the source metadata. Government data portals typically declare the coordinate system in the dataset's description or metadata XML.
  4. Load in QGIS as a quick visual sanity check. If the data appears in the right place when loaded, QGIS has auto-detected the CRS correctly and can show you the EPSG code.

Class Description
Projection Wraps a single CRS definition (used when configuring ProjectionConverter in XAML via Srid property)
FeatureSource Base class for all data sources; exposes the ProjectionConverter property
ShapeFileFeatureSource Most common feature source; projection is almost always needed
GeographyUnit Enum used to set MapView.MapUnit — must match the external projection
Feature A single geographic feature; the object passed to ConvertToExternalProjection / ConvertToInternalProjection
RectangleShape Bounding box; coordinates must be in the map's CRS when used with MapView.CurrentExtent

Further Reading