Skip to content

Coordinates

Geographic and projected coordinates can be represented as single positions, series of positions and bounding boxes (with minimum and maximum positions).

This chapter discusses using and creating such objects.

Geometric objects commonly used in geospatial applications like like points, line strings (or polylines) and polygons are introduced in the separate chapter about simple geometries.

📍 Position data

The basic building blocks to represent position data in this package are:

ClassDescription
PositionA position with 2 to 4 coordinate values (x and y are required, z and m are optional) representing an exact location in some coordinate reference system.
PositionSeriesA series of 0 to N positions built from a coordinate value array or a list of position objects.
BoxA bounding box with 4 to 8 coordinate values (minX, minY, maxX and maxY are required, minZ, minM, maxZ and maxM are optional).

These classes are used by simple geometry classes too as internal data structures to store single positions and boxes, and series of positions.

Some basic samples to create position objects:

// A position as a view on a coordinate array containing x and y.
Position.view([708221.0, 5707225.0]);
// A position as a view on a coordinate array containing x, y and z.
Position.view([708221.0, 5707225.0, 45.0]);
// A position as a view on a coordinate array containing x, y, z and m.
Position.view([708221.0, 5707225.0, 45.0, 123.0]);
// The samples above can be shorted using extension methods on `List<double>`.
[708221.0, 5707225.0].xy;
[708221.0, 5707225.0, 45.0].xyz;
[708221.0, 5707225.0, 45.0, 123.0].xyzm;
// There are also some other factory methods.
Position.create(x: 708221.0, y: 5707225.0, z: 45.0, m: 123.0);
Position.parse('708221.0,5707225.0,45.0,123.0');
Position.parse('708221.0 5707225.0 45.0 123.0', delimiter: ' ');

When a position contains geographic coordinates, then x represents longitude, y represents latitude, and z represents elevation (or height or altitude) when manipulating data with classes introduced here (external data formats may use different axis orders).

Bounding boxes have similar factory methods too:

// The same bounding box (limits on x and y) created with different factories.
Box.view([70800.0, 5707200.0, 70900.0, 5707300.0]);
Box.create(minX: 70800.0, minY: 5707200.0, maxX: 70900.0, maxY: 5707300.0);
Box.parse('70800.0,5707200.0,70900.0,5707300.0');
Box.parse('70800.0 5707200.0 70900.0 5707300.0', delimiter: ' ');
// The same box using extension methods on `List<double>`.
[70800.0, 5707200.0, 70900.0, 5707300.0].box;

🪣 Series of positions

PositionSeries is a fixed-length (and random-access) view to a series of positions. There are two main structures to store coordinate values of positions contained in a series:

  • A list of Position objects (each object contains x and y coordinates, and optionally z and m too).
  • A list of double values as a flat structure. For example a double list could contain coordinates like [x0, y0, z0, x1, y1, z1, x2, y2, z2] that represents three positions each with x, y and z coordinates.

These two structures are demonstrated by code:

// A position series from a flat coordinate value array.
PositionSeries.view(
[
70800.0, 5707200.0, // (x, y) coordinate values for position 0
70850.0, 5707250.0, // (x, y) coordinate values for position 1
70900.0, 5707300.0, // (x, y) coordinate values for position 2
],
type: Coords.xy,
);
// A position series from an array of position objects.
PositionSeries.from(
[
[70800.0, 5707200.0].xy, // position 0 with (x, y) coordinate values
[70850.0, 5707250.0].xy, // position 1 with (x, y) coordinate values
[70900.0, 5707300.0].xy, // position 2 with (x, y) coordinate values
],
type: Coords.xy,
);

When manipulating positions in code it may be easier to handle lists or iterables of Position objects. However coordinate value arrays in a flat structure is used by default when decoding position data from geospatial data formats.

Coordinate value arrays represented as List<double> also provides more memory-efficient structure than List<Position>. This is further enhanced by using optimized Float64List (a list of double-precision floating-point numbers) or Float32List (a list of single-precision floating-point numbers, takes even less space but sacrifices accuracy a bit) data structures defined by the standard dart:typed_data package.

Building PositionSeries objects from coordinate value arrays can be also shortened. This can be handy when specifying position data in Dart code.

// A position series from a flat coordinate value array (2D positions).
[
70800.0, 5707200.0, // (x, y) coordinate values for position 0
70850.0, 5707250.0, // (x, y) coordinate values for position 1
70900.0, 5707300.0, // (x, y) coordinate values for position 2
].positions(Coords.xy);
// A position series from a flat coordinate value array (3D positions).
[
70800.0, 5707200.0, 40.0, // (x, y, z) coordinate values for position 0
70850.0, 5707250.0, 45.0, // (x, y, z) coordinate values for position 1
70900.0, 5707300.0, 50.0, // (x, y, z) coordinate values for position 2
].positions(Coords.xyz);

See also the advanced topic about coordinate arrays for more information about handling coordinate value arrays for a single position, series of positions and a single bounding box.

Classes described above can be used to represented position data in various coordinate reference systems, including geographic, projected and local systems.

There are also very specific subtypes of Position and Box classes.

Projected (extending Position) and ProjBox (extending Box) can be used to represent projected or cartesian (XYZ) coordinates. Similarily Geographic and GeoBox can be used to represent geographic coordinates.

These special purpose subtypes for positions and boxes are discussed in next few sections.

🌐 Geographic coordinates

Geographic coordinates are based on a spherical or ellipsoidal coordinate system representing positions on the Earth as longitude (lon) and latitude (lat).

Elevation (elev) in meters and measure (m) coordinates are optional.

Latitude and Longitude of the Earth

Geographic positions:

// A geographic position with longitude and latitude.
Geographic(lon: -0.0014, lat: 51.4778);
// A geographic position with longitude, latitude and elevation.
Geographic(lon: -0.0014, lat: 51.4778, elev: 45.0);
// A geographic position with longitude, latitude, elevation and measure.
Geographic(lon: -0.0014, lat: 51.4778, elev: 45.0, m: 123.0);
// The last sample also from a double list or text (order: lon, lat, elev, m).
Geographic.build([-0.0014, 51.4778, 45.0, 123.0]);
Geographic.parse('-0.0014,51.4778,45.0,123.0');
Geographic.parse('-0.0014 51.4778 45.0 123.0', delimiter: ' ');

Geographic bounding boxes:

// A geographic bbox (-20 .. 20 in longitude, 50 .. 60 in latitude).
GeoBox(west: -20, south: 50, east: 20, north: 60);
// A geographic bbox with limits (100 .. 200) on the elevation coordinate too.
GeoBox(west: -20, south: 50, minElev: 100, east: 20, north: 60, maxElev: 200);
// The last sample also from a double list or text.
GeoBox.build([-20, 50, 100, 20, 60, 200]);
GeoBox.parse('-20,50,100,20,60,200');

🔢 Geographic string representations (DMS)

A geographic position can also be parsed from sexagesimal degrees (latitude and longitude subdivided to degrees, minutes and seconds):

// Decimal degrees (DD) with signed numeric degree values.
Geographic.parseDms(lat: '51.4778', lon: '-0.0014');
// Decimal degrees (DD) with degree and cardinal direction symbols (N/E/S/W).
Geographic.parseDms(lat: '51.4778°N', lon: '0.0014°W');
// Degrees and minutes (DM).
Geographic.parseDms(lat: '51°28.668′N', lon: '0°00.084′W');
// Degrees, minutes and seconds (DMS).
Geographic.parseDms(lat: '51° 28′ 40″ N', lon: '0° 00′ 05″ W');

Format geographic coordinates as string representations (DD, DM, DMS):

const p = Geographic(lat: 51.4778, lon: -0.0014);
// all three samples print decimal degrees: 51.4778°N 0.0014°W
print(p.latLonDms(separator: ' '));
print('${p.latDms()} ${p.lonDms()}');
print('${Dms().lat(51.4778)} ${Dms().lon(-0.0014)}');
// prints degrees and minutes: 51°28.668′N, 0°00.084′W
const dm = Dms(type: DmsType.degMin, decimals: 3);
print(p.latLonDms(format: dm));
// prints degrees, minutes and seconds: 51° 28′ 40″ N, 0° 00′ 05″ W
const dms = Dms.narrowSpace(type: DmsType.degMinSec);
print(p.latLonDms(format: dms));
// 51 degrees 28 minutes 40 seconds to N, 0 degrees 0 minutes 5 seconds to W
const dmsTextual = Dms(
type: DmsType.degMinSec,
separator: ' ',
decimals: 0,
zeroPadMinSec: false,
degree: ' degrees',
prime: ' minutes',
doublePrime: ' seconds to',
);
print(p.latLonDms(format: dmsTextual));

Parsing and formatting is supported also for geographic bounding boxes:

// Parses box from decimal degrees (DD) with cardinal direction symbols.
final box =
GeoBox.parseDms(west: '20°W', south: '50°N', east: '20°E', north: '60°N');
// prints degrees and minutes: 20°0′W 50°0′N, 20°0′E 60°0′N
const dm0 = Dms(type: DmsType.degMin, decimals: 0, zeroPadMinSec: false);
print('${box.westDms(dm0)} ${box.southDms(dm0)}'
' ${box.eastDms(dm0)} ${box.northDms(dm0)}');

In the previous example dm, dm0, dms and dmsTextual are instances of the Dms class that implements DmsFormat. This defines multiple methods for parsing and formatting decimal degrees and sexagesimal degrees (degrees/minutes/seconds) on latitude, longitude and bearing values.

The default format used by Geographic and GeoBox classes formats values as decimal degrees with cardinal direction symbols. To use other formats (degrees/minutes or degrees/minutes/seconds), or to set other parameters (like separators, symbol characters, the number of decimals, zero padding or value signing) you should create a custom Dms instance.

See the API documentation and DMS test cases for more samples.

🗺️ Projected coordinates

Projected coordinates represent projected or cartesian (XYZ) coordinates with an optional measure (m) coordinate. For projected map positions x often represents easting (E) and y represents northing (N), however a coordinate reference system might specify something else too.

The m (measure) coordinate represents a measurement or a value on a linear referencing system (like time). It could be associated with a 2D position (x, y, m) or a 3D position (x, y, z, m).

Projected positions:

// A projected position with x and y.
Projected(x: 708221.0, y: 5707225.0);
// A projected position with x, y and z.
Projected(x: 708221.0, y: 5707225.0, z: 45.0);
// A projected position with x, y, z and m.
Projected(x: 708221.0, y: 5707225.0, z: 45.0, m: 123.0);
// The last sample also from a double list or text (order: x, y, z, m).
Projected.build([708221.0, 5707225.0, 45.0, 123.0]);
Projected.parse('708221.0,5707225.0,45.0,123.0');
Projected.parse('708221.0 5707225.0 45.0 123.0', delimiter: ' ');

Projected bounding boxes:

// A projected bbox with limits on x and y.
ProjBox(minX: 10, minY: 10, maxX: 20, maxY: 20);
// A projected bbox with limits on x, y and z.
ProjBox(minX: 10, minY: 10, minZ: 10, maxX: 20, maxY: 20, maxZ: 20);
// The last sample also from a double list or text.
ProjBox.build([10, 10, 10, 20, 20, 20]);
ProjBox.parse('10,10,10,20,20,20');

🗄️ Scalable coordinates

Scalable coordinates are coordinates associated with a level of detail (LOD) or a zoom level. They are used for example by tiling schemes to represent pixels or tiles in tile matrices.

The Scalable2i class represents projected x, y coordinates at zoom level, with all values as integers.

// A pixel with a zoom level (or LOD = level of detail) coordinates.
const pixel = Scalable2i(zoom: 9, x: 23, y: 10);
// Such coordinates can be scaled to other zoom levels.
pixel.zoomIn(); // => Scalable2i(zoom: 10, x: 46, y: 20);
pixel.zoomOut(); // => Scalable2i(zoom: 8, x: 11, y: 5);
pixel.zoomTo(13); // => Scalable2i(zoom: 13, x: 368, y: 160));

⌖ Coordinates summary

Classes representing position, bounding box and scalable coordinates:

Coordinate values in position classes (projected and geographic):

ClassRequired coordinatesOptional coordinatesValues
Positionx, yz, mdouble
Projectedx, yz, mdouble
Geographiclon, latelev, mdouble

Coordinate values in bounding box classes (projected and geographic):

ClassRequired coordinatesOptional coordinatesValues
BoxminX, minY, maxX, maxYminZ, minM, maxZ, maxMdouble
ProjBoxminX, minY, maxX, maxYminZ, minM, maxZ, maxMdouble
GeoBoxwest, south, east, northminElev, minM, maxElev, maxMdouble

Coordinate values in scalable classes:

ClassRequired coordinatesOptional coordinatesValues
Scalable2izoom, x, yint

Coordinates are stored as double values in all position and bounding box classes but Scalable2i uses int coordinate values.

The Position class is a super type for Projected and Geographic, and the Box class is a super type for ProjBox and GeoBox. Please see more information about them in the API reference.

📡 Coordinate reference systems

According to Wikipedia a Coordinate reference system is a coordinate-based local, regional or global system used to locate geographical entities.

Coordinate reference systems are identified by String identifiers. Such ids are specified by registries like The EPSG dataset and OGC CRS registry. Also W3C has a good introduction about coordinate reference systems.

The package also contains CoordRefSys class that has constant instaces for:

ConstantDescription
CRS84WGS 84 geographic coordinates (order: longitude, latitude).
CRS84hWGS 84 geographic coordinates (order: longitude, latitude) with ellipsoidal height (elevation).
EPSG_4326WGS 84 geographic coordinates (order: latitude, longitude).
EPSG_4258ETRS89 geographic coordinates (order: latitude, longitude).
EPSG_3857WGS 84 projected (Web Mercator) metric coordinates based on “spherical development of ellipsoidal coordinates”.
EPSG_3395WGS 84 projected (World Mercator) metric coordinates based on “ellipsoidal coordinates”.

These constants can be used to check id, axisOrder and whether to swapXY when dealing with external data sources like GeoJSON data. However currently geodetic datum and projection parameters are not available.

Other identifiers can be added by creating a custom class implementing CoordRefSysResolver and by registering it’s global instance using CoordRefSysResolver.register() on startup routines of your app.

Please note that CRS84 and EPSG_4326 (or “EPSG:4326”) constants both refer to the WGS 84 geographic coordinate system, but in external data representations their axis order differs.

📅 Temporal coordinate reference systems

There is also a type TemporalRefSys for specifying a temporal coordinate reference system. A custom logic can be registered using TemporalRefSysResolver.register().

Currently there is only one constant identifier defined by TemporalRefSys:

ConstantDescription
gregorianReferences temporal coordinates, dates or timestamps, that are in the Gregorian calendar and conform to RFC 3339.