Appendix B: Coordinate Systems and Projections#
This appendix provides a comprehensive reference for coordinate systems and map projections commonly used in Web GIS applications. Understanding coordinate systems is crucial for accurate spatial data handling, coordinate transformations, and map display.
Coordinate System Fundamentals#
Geographic Coordinate Systems (GCS)#
Geographic coordinate systems use angular units (degrees) to specify locations on the Earth’s surface using latitude and longitude coordinates.
Key Components:
Datum: Mathematical model of the Earth’s shape
Prime Meridian: Reference longitude (usually Greenwich at 0°)
Angular Unit: Typically decimal degrees
Common Geographic Coordinate Systems:
WGS84 (EPSG:4326)#
Full Name: World Geodetic System 1984
Usage: GPS, web mapping, international standards
Extent: Global
Prime Meridian: Greenwich
Ellipsoid: WGS 84
// WGS84 coordinate example
const wgs84Point = {
latitude: 37.7749, // North is positive
longitude: -122.4194, // West is negative
srs: "EPSG:4326",
};
NAD83 (EPSG:4269)#
Full Name: North American Datum 1983
Usage: North American mapping and surveying
Extent: North America
Ellipsoid: GRS 1980
Accuracy: Higher precision than WGS84 for North America
NAD27 (EPSG:4267)#
Full Name: North American Datum 1927
Usage: Legacy North American data
Extent: North America
Ellipsoid: Clarke 1866
Note: Superseded by NAD83
Projected Coordinate Systems (PCS)#
Projected coordinate systems transform the curved Earth surface onto a flat plane using mathematical projections, with coordinates typically in linear units (meters, feet).
Key Components:
Projection Method: Mathematical transformation algorithm
Parameters: False easting, false northing, central meridian, etc.
Linear Unit: Meters, feet, etc.
Geographic Coordinate System: Underlying datum
Web Mercator Projection (EPSG:3857)#
Web Mercator is the most important projection for web mapping applications.
Characteristics#
Also Known As: Pseudo-Mercator, Spherical Mercator
EPSG Codes: 3857, 900913 (deprecated)
Units: Meters
Extent: -20037508.34 to 20037508.34 (x and y)
Latitude Range: Approximately ±85.05°
Mathematical Definition#
// Web Mercator projection formulas
class WebMercator {
static readonly EARTH_RADIUS = 6378137; // WGS84 equatorial radius
static readonly MAX_EXTENT = 20037508.342789244;
// Convert lat/lon to Web Mercator coordinates
static project([lng, lat]) {
const x = lng * this.EARTH_RADIUS * Math.PI / 180;
const y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) * this.EARTH_RADIUS;
return [x, y];
}
// Convert Web Mercator coordinates to lat/lon
static unproject([x, y]) {
const lng = x / this.EARTH_RADIUS * 180 / Math.PI;
const lat = (2 * Math.atan(Math.exp(y / this.EARTH_RADIUS)) - Math.PI / 2) * 180 / Math.PI;
return [lng, lat];
}
// Check if coordinates are within valid Web Mercator bounds
static isValid([x, y]) {
return Math.abs(x) <= this.MAX_EXTENT && Math.abs(y) <= this.MAX_EXTENT;
}
}
Usage in Web GIS#
Advantages:
Universal adoption by web mapping platforms
Simple mathematical properties
Fast calculation
Square pixels at all zoom levels
Limitations:
Significant distortion at high latitudes
Cannot display polar regions
Area distortion increases away from equator
Example Implementation:
// Tile coordinate calculation in Web Mercator
function getWebMercatorTile(lat, lng, zoom) {
// Project to Web Mercator
const [x, y] = WebMercator.project([lng, lat]);
// Convert to tile coordinates
const tileSize = 256;
const resolution =
(2 * WebMercator.MAX_EXTENT) / (tileSize * Math.pow(2, zoom));
const tileX = Math.floor(
(x + WebMercator.MAX_EXTENT) / (resolution * tileSize)
);
const tileY = Math.floor(
(WebMercator.MAX_EXTENT - y) / (resolution * tileSize)
);
return { x: tileX, y: tileY, z: zoom };
}
State Plane Coordinate Systems#
State Plane Coordinate Systems (SPCS) provide high-accuracy coordinate systems for specific US states and regions.
SPCS 1983 (NAD83)#
Based on NAD83 datum, using either Lambert Conformal Conic or Transverse Mercator projections.
Common Examples:
California Zone 3 (EPSG:2227)#
Projection: Lambert Conformal Conic
Units: US Survey Feet
Central Meridian: -120.5°
Standard Parallels: 37.066667°, 38.433333°
// Example coordinate transformation to California State Plane
const californiaZone3 = {
epsg: 2227,
proj4:
"+proj=lcc +lat_1=37.06666666666667 +lat_2=38.43333333333333 +lat_0=36.5 +lon_0=-120.5 +x_0=609601.2192024384 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs",
};
New York Long Island (EPSG:2263)#
Projection: Lambert Conformal Conic
Units: US Survey Feet
Central Meridian: -74°
Standard Parallels: 40.666667°, 41.033333°
SPCS 1927 (NAD27)#
Legacy system based on NAD27 datum, still used for some historical data.
UTM (Universal Transverse Mercator)#
UTM divides the Earth into 60 zones, each 6° wide, using Transverse Mercator projection.
Zone Characteristics#
Zone Width: 6° longitude
Zone Numbering: 1-60, starting at 180°W
Hemisphere: N (Northern) or S (Southern)
Central Scale Factor: 0.9996
False Easting: 500,000 meters
False Northing: 0 (North), 10,000,000 (South)
Zone Calculation#
function getUTMZone(longitude, latitude) {
// Calculate zone number
const zoneNumber = Math.floor((longitude + 180) / 6) + 1;
const hemisphere = latitude >= 0 ? "N" : "S";
return {
zone: zoneNumber,
hemisphere: hemisphere,
epsg: latitude >= 0 ? 32600 + zoneNumber : 32700 + zoneNumber,
};
}
// Example usage
const utmZone = getUTMZone(-122.4194, 37.7749); // Zone 10N
console.log(utmZone); // { zone: 10, hemisphere: 'N', epsg: 32610 }
UTM Zone Coverage#
Zone Examples:
Zone 10N (EPSG:32610): US West Coast
Zone 11N (EPSG:32611): US Southwest
Zone 17N (EPSG:32617): US East Coast
Zone 33N (EPSG:32633): Central Europe
Coordinate Transformations#
Using Proj4js#
Proj4js is the standard JavaScript library for coordinate transformations.
import proj4 from "proj4";
// Define coordinate systems
proj4.defs(
"EPSG:2227",
"+proj=lcc +lat_1=37.06666666666667 +lat_2=38.43333333333333 +lat_0=36.5 +lon_0=-120.5 +x_0=609601.2192024384 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs"
);
// Transform coordinates
const wgs84 = [-122.4194, 37.7749];
const statePlane = proj4("EPSG:4326", "EPSG:2227", wgs84);
console.log(statePlane); // [6005574.12, 2089471.84] (approximate)
// Create reusable transformer
const transformer = proj4("EPSG:4326", "EPSG:3857");
const webMercator = transformer.forward([-122.4194, 37.7749]);
Transformation Pipeline#
class CoordinateTransformer {
constructor() {
this.transformers = new Map();
this.initializeCommonSystems();
}
initializeCommonSystems() {
// Define common coordinate systems
const definitions = {
"EPSG:4326": "+proj=longlat +datum=WGS84 +no_defs",
"EPSG:3857":
"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs",
"EPSG:32610": "+proj=utm +zone=10 +datum=WGS84 +units=m +no_defs",
};
Object.entries(definitions).forEach(([code, definition]) => {
proj4.defs(code, definition);
});
}
transform(coordinates, fromSRS, toSRS) {
const key = `${fromSRS}-${toSRS}`;
if (!this.transformers.has(key)) {
this.transformers.set(key, proj4(fromSRS, toSRS));
}
const transformer = this.transformers.get(key);
return transformer.forward(coordinates);
}
transformBounds(bounds, fromSRS, toSRS) {
const [minX, minY, maxX, maxY] = bounds;
// Transform corner points
const bottomLeft = this.transform([minX, minY], fromSRS, toSRS);
const topRight = this.transform([maxX, maxY], fromSRS, toSRS);
const bottomRight = this.transform([maxX, minY], fromSRS, toSRS);
const topLeft = this.transform([minX, maxY], fromSRS, toSRS);
// Find new bounds
const allX = [bottomLeft[0], topRight[0], bottomRight[0], topLeft[0]];
const allY = [bottomLeft[1], topRight[1], bottomRight[1], topLeft[1]];
return [
Math.min(...allX),
Math.min(...allY),
Math.max(...allX),
Math.max(...allY),
];
}
isValidTransformation(fromSRS, toSRS) {
try {
this.transform([0, 0], fromSRS, toSRS);
return true;
} catch (error) {
return false;
}
}
}
Projection Properties and Selection#
Projection Categories#
Conformal Projections#
Property: Preserve angles and shapes locally
Examples: Mercator, Lambert Conformal Conic, Transverse Mercator
Use Cases: Navigation, topographic maps, web mapping
Equal-Area Projections#
Property: Preserve area relationships
Examples: Albers Equal Area, Lambert Azimuthal Equal Area
Use Cases: Statistical mapping, density analysis
Equidistant Projections#
Property: Preserve distances from specific points
Examples: Azimuthal Equidistant, Equidistant Conic
Use Cases: Distance analysis, radio transmission maps
Distortion Analysis#
class ProjectionAnalyzer {
// Calculate scale factor at a point
static getScaleFactor(projection, longitude, latitude) {
const deltaLon = 0.001;
const deltaLat = 0.001;
// Project center point and nearby points
const center = proj4(projection, [longitude, latitude]);
const east = proj4(projection, [longitude + deltaLon, latitude]);
const north = proj4(projection, [longitude, latitude + deltaLat]);
// Calculate distances
const distanceEast = Math.hypot(east[0] - center[0], east[1] - center[1]);
const distanceNorth = Math.hypot(
north[0] - center[0],
north[1] - center[1]
);
// Calculate expected distances (on sphere)
const earthRadius = 6378137;
const expectedEast =
((earthRadius * deltaLon * Math.PI) / 180) *
Math.cos((latitude * Math.PI) / 180);
const expectedNorth = (earthRadius * deltaLat * Math.PI) / 180;
return {
scaleX: distanceEast / expectedEast,
scaleY: distanceNorth / expectedNorth,
avgScale:
(distanceEast / expectedEast + distanceNorth / expectedNorth) / 2,
};
}
// Analyze distortion across a region
static analyzeDistortion(projection, bounds, gridSize = 10) {
const [minX, minY, maxX, maxY] = bounds;
const stepX = (maxX - minX) / gridSize;
const stepY = (maxY - minY) / gridSize;
const analysis = [];
for (let i = 0; i <= gridSize; i++) {
for (let j = 0; j <= gridSize; j++) {
const lon = minX + i * stepX;
const lat = minY + j * stepY;
const scaleFactor = this.getScaleFactor(projection, lon, lat);
analysis.push({
longitude: lon,
latitude: lat,
...scaleFactor,
});
}
}
return analysis;
}
}
Common Web GIS Coordinate Systems#
Global Systems#
EPSG Code |
Name |
Type |
Usage |
|---|---|---|---|
4326 |
WGS84 |
Geographic |
GPS, web services, global data |
3857 |
Web Mercator |
Projected |
Web mapping, tile services |
4269 |
NAD83 |
Geographic |
North American data |
3395 |
World Mercator |
Projected |
Global mapping (true Mercator) |
Regional Systems (United States)#
EPSG Code |
Name |
Coverage |
Units |
|---|---|---|---|
2227 |
CA State Plane Zone 3 |
Central California |
US Survey Feet |
2263 |
NY State Plane Long Island |
New York |
US Survey Feet |
2248 |
MD State Plane |
Maryland |
Meters |
32610 |
UTM Zone 10N |
US West Coast |
Meters |
32617 |
UTM Zone 17N |
US East Coast |
Meters |
European Systems#
EPSG Code |
Name |
Coverage |
Usage |
|---|---|---|---|
4258 |
ETRS89 |
Europe |
Pan-European datum |
3035 |
ETRS89 LAEA |
Europe |
Statistical analysis |
25832 |
ETRS89 UTM 32N |
Central Europe |
Mapping |
27700 |
British National Grid |
United Kingdom |
UK mapping |
Coordinate System Detection#
class CoordinateSystemDetector {
static detectFromBounds(bounds) {
const [minX, minY, maxX, maxY] = bounds;
// Check Web Mercator bounds
if (
Math.abs(minX) <= 20037508.34 &&
Math.abs(maxX) <= 20037508.34 &&
Math.abs(minY) <= 20037508.34 &&
Math.abs(maxY) <= 20037508.34 &&
(Math.abs(minX) > 180 || Math.abs(maxX) > 180)
) {
return "EPSG:3857";
}
// Check WGS84 bounds
if (minX >= -180 && maxX <= 180 && minY >= -90 && maxY <= 90) {
return "EPSG:4326";
}
// Check UTM bounds (rough estimate)
if (
minX > 100000 &&
maxX < 900000 &&
((minY > 0 && maxY < 10000000) || (minY > 9000000 && maxY < 20000000))
) {
return "UTM"; // Further analysis needed for specific zone
}
// Check State Plane bounds (US Survey Feet)
if (minX > 1000000 && maxX < 10000000 && minY > 0 && maxY < 10000000) {
return "STATE_PLANE_FEET";
}
return "UNKNOWN";
}
static detectUTMZone(bounds) {
// Convert bounds to geographic coordinates first
const [minX, minY, maxX, maxY] = bounds;
const centerX = (minX + maxX) / 2;
const centerY = (minY + maxY) / 2;
// Estimate zone from center coordinates
if (centerX > 100000 && centerX < 900000) {
// Likely UTM coordinates
const estimatedLon = (centerX - 500000) / 111320 - 183; // Rough estimate
const zoneNumber = Math.floor((estimatedLon + 180) / 6) + 1;
const hemisphere = centerY > 5000000 ? "N" : "S";
return {
zone: zoneNumber,
hemisphere: hemisphere,
epsg: hemisphere === "N" ? 32600 + zoneNumber : 32700 + zoneNumber,
};
}
return null;
}
static validateCoordinateSystem(coordinates, expectedSRS) {
const validRanges = {
"EPSG:4326": { x: [-180, 180], y: [-90, 90] },
"EPSG:3857": {
x: [-20037508.34, 20037508.34],
y: [-20037508.34, 20037508.34],
},
};
const range = validRanges[expectedSRS];
if (!range) return true; // Can't validate unknown systems
const [x, y] = coordinates;
return (
x >= range.x[0] && x <= range.x[1] && y >= range.y[0] && y <= range.y[1]
);
}
}
Best Practices#
Coordinate System Selection#
Web Mapping: Use EPSG:3857 (Web Mercator) for tile-based maps
Data Storage: Store original data in EPSG:4326 (WGS84) when possible
Analysis: Use appropriate equal-area or conformal projections based on analysis type
Regional Work: Use local coordinate systems (State Plane, UTM) for high accuracy
Data Handling#
// Best practices for coordinate handling
class CoordinateHandler {
constructor(defaultSRS = 'EPSG:4326') {
this.defaultSRS = defaultSRS;
this.transformer = new CoordinateTransformer();
}
// Always validate input coordinates
validateInput(coordinates, srs) {
if (!Array.isArray(coordinates) || coordinates.length < 2) {
throw new Error('Invalid coordinate format');
}
if (!CoordinateSystemDetector.validateCoordinateSystem(coordinates, srs)) {
throw new Error(`Coordinates out of range for ${srs}`);
}
return true;
}
// Standardize coordinates to a common system
standardize(coordinates, sourceSRS, targetSRS = this.defaultSRS) {
this.validateInput(coordinates, sourceSRS);
if (sourceSRS === targetSRS) {
return coordinates;
}
return this.transformer.transform(coordinates, sourceSRS, targetSRS);
}
// Handle coordinate precision
roundCoordinates(coordinates, precision = 6) {
return coordinates.map(coord =>
Math.round(coord * Math.pow(10, precision)) / Math.pow(10, precision)
);
}
// Create coordinate metadata
createMetadata(coordinates, srs, source = 'unknown') {
return {
coordinates: coordinates,
srs: srs,
precision: this.detectPrecision(coordinates),
source: source,
timestamp: new Date().toISOString()
};
}
private detectPrecision(coordinates) {
return Math.max(...coordinates.map(coord => {
const str = coord.toString();
const decimal = str.indexOf('.');
return decimal === -1 ? 0 : str.length - decimal - 1;
}));
}
}
Common Pitfalls and Solutions#
Axis Order: Some systems use (lat, lon) instead of (lon, lat)
Unit Confusion: Always verify units (degrees vs. meters vs. feet)
Datum Differences: NAD27 vs. NAD83 can differ by meters
Precision Loss: Be careful with coordinate precision in transformations
Bounds Checking: Always validate coordinates are within expected ranges
This reference provides the foundation for understanding and working with coordinate systems in Web GIS applications. Proper coordinate system handling is essential for accurate spatial analysis and mapping applications.