Skip to content

Commit 41bfee5

Browse files
committed
Added toWGS84 and toMercator as member functions of the coordinate types
1 parent f6c0e41 commit 41bfee5

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed

lib/src/helpers.dart

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,27 @@ enum DistanceGeometry {
4646
/// Earth Radius used with the Harvesine formula and approximates using a spherical (non-ellipsoid) Earth.
4747
const earthRadius = 6371008.8;
4848

49+
/// Maximum extent of the Web Mercator projection in meters
50+
const double mercatorLimit = 20037508.34;
51+
52+
/// Earth radius in meters used for coordinate system conversions
53+
const double conversionEarthRadius = 6378137.0;
54+
55+
/// Coordinate reference systems for spatial data
56+
enum CoordinateSystem {
57+
/// WGS84 geographic coordinates (longitude/latitude)
58+
wgs84,
59+
60+
/// Web Mercator projection (EPSG:3857)
61+
mercator,
62+
}
63+
64+
/// Coordinate system conversion constants
65+
const coordSystemConstants = {
66+
'mercatorLimit': mercatorLimit,
67+
'earthRadius': conversionEarthRadius,
68+
};
69+
4970
/// Unit of measurement factors using a spherical (non-ellipsoid) earth radius.
5071
/// Keys are the name of the unit, values are the number of that unit in a single radian
5172
const factors = <Unit, num>{
@@ -180,3 +201,80 @@ num convertArea(num area,
180201

181202
return (area / startFactor) * finalFactor;
182203
}
204+
205+
206+
/// Converts coordinates from one system to another
207+
/// Valid systems: wgs84, mercator
208+
/// Returns: Array of coordinates in the target system
209+
List<double> convertCoordinates(
210+
List<num> coord,
211+
CoordinateSystem fromSystem,
212+
CoordinateSystem toSystem
213+
) {
214+
if (fromSystem == toSystem) {
215+
return coord.map((e) => e.toDouble()).toList();
216+
}
217+
218+
if (fromSystem == CoordinateSystem.wgs84 && toSystem == CoordinateSystem.mercator) {
219+
return toMercator(coord);
220+
} else if (fromSystem == CoordinateSystem.mercator && toSystem == CoordinateSystem.wgs84) {
221+
return toWGS84(coord);
222+
} else {
223+
throw Exception("Unsupported coordinate system conversion: $fromSystem to $toSystem");
224+
}
225+
}
226+
227+
/// Converts a WGS84 coordinate to Web Mercator
228+
/// Valid inputs: Array of [longitude, latitude]
229+
/// Returns: Array of [x, y] coordinates in meters
230+
List<double> toMercator(List<num> coord) {
231+
if (coord.length < 2) {
232+
throw Exception("coordinates must contain at least 2 values");
233+
}
234+
235+
// Use the earth radius constant for consistency
236+
237+
// Clamp latitude to avoid infinite values near the poles
238+
final longitude = coord[0].toDouble();
239+
final latitude = max(min(coord[1].toDouble(), 89.99), -89.99);
240+
241+
// Convert longitude to x coordinate
242+
final x = longitude * (conversionEarthRadius * pi / 180.0);
243+
244+
// Convert latitude to y coordinate
245+
final latRad = latitude * (pi / 180.0);
246+
final y = log(tan((pi / 4) + (latRad / 2))) * conversionEarthRadius;
247+
248+
// Clamp to valid Mercator bounds
249+
final clampedX = max(min(x, mercatorLimit), -mercatorLimit);
250+
final clampedY = max(min(y, mercatorLimit), -mercatorLimit);
251+
252+
return [clampedX, clampedY];
253+
}
254+
255+
/// Converts a Web Mercator coordinate to WGS84
256+
/// Valid inputs: Array of [x, y] in meters
257+
/// Returns: Array of [longitude, latitude] coordinates
258+
List<double> toWGS84(List<num> coord) {
259+
if (coord.length < 2) {
260+
throw Exception("coordinates must contain at least 2 values");
261+
}
262+
263+
// Use the earth radius constant for consistency
264+
265+
// Clamp inputs to valid range
266+
final x = max(min(coord[0].toDouble(), mercatorLimit), -mercatorLimit);
267+
final y = max(min(coord[1].toDouble(), mercatorLimit), -mercatorLimit);
268+
269+
// Convert x to longitude
270+
final longitude = x / (conversionEarthRadius * pi / 180.0);
271+
272+
// Convert y to latitude
273+
final latRad = 2 * atan(exp(y / conversionEarthRadius)) - (pi / 2);
274+
final latitude = latRad * (180.0 / pi);
275+
276+
// Clamp latitude to valid range
277+
final clampedLatitude = max(min(latitude, 90.0), -90.0);
278+
279+
return [longitude, clampedLatitude];
280+
}

test/components/helpers_test.dart

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:math';
22
import 'package:test/test.dart';
33
import 'package:turf/helpers.dart';
4+
import 'package:geotypes/geotypes.dart';
45

56
void main() {
67
test('radiansToLength', () {
@@ -77,4 +78,98 @@ void main() {
7778
expect(convertArea(100, Unit.meters, Unit.feet), equals(1076.3910417));
7879
expect(convertArea(100000, Unit.feet), equals(0.009290303999749462));
7980
});
81+
82+
test('toMercator', () {
83+
// Test with San Francisco coordinates
84+
final wgs84 = [-122.4194, 37.7749];
85+
final mercator = toMercator(wgs84);
86+
87+
// Expected values (approximate)
88+
final expectedX = -13627665.0;
89+
final expectedY = 4547675.0;
90+
91+
// Check conversion produces results within an acceptable range
92+
expect(mercator[0], closeTo(expectedX, 50.0));
93+
expect(mercator[1], closeTo(expectedY, 50.0));
94+
95+
// Test with error case
96+
expect(() => toMercator([]), throwsException);
97+
});
98+
99+
test('toWGS84', () {
100+
// Test with San Francisco Mercator coordinates
101+
final mercator = [-13627695.092862014, 4547675.345836067];
102+
final wgs84 = toWGS84(mercator);
103+
104+
// Expected values (approximate)
105+
final expectedLon = -122.42;
106+
final expectedLat = 37.77;
107+
108+
// Check conversion produces results within an acceptable range
109+
expect(wgs84[0], closeTo(expectedLon, 0.01));
110+
expect(wgs84[1], closeTo(expectedLat, 0.01));
111+
112+
// Test with error case
113+
expect(() => toWGS84([]), throwsException);
114+
});
115+
116+
test('Round-trip conversion WGS84-Mercator-WGS84', () {
117+
// Test coordinates for various cities
118+
final cities = [
119+
[-122.4194, 37.7749], // San Francisco
120+
[139.6917, 35.6895], // Tokyo
121+
[151.2093, -33.8688], // Sydney
122+
[-0.1278, 51.5074], // London
123+
];
124+
125+
for (final original in cities) {
126+
final mercator = toMercator(original);
127+
final roundTrip = toWGS84(mercator);
128+
129+
// Round-trip should return to the original value within a small delta
130+
expect(roundTrip[0], closeTo(original[0], 0.00001));
131+
expect(roundTrip[1], closeTo(original[1], 0.00001));
132+
}
133+
});
134+
135+
test('convertCoordinates', () {
136+
// Test WGS84 to Mercator conversion
137+
final wgs84 = [-122.4194, 37.7749]; // San Francisco
138+
final mercator = convertCoordinates(
139+
wgs84,
140+
CoordinateSystem.wgs84,
141+
CoordinateSystem.mercator
142+
);
143+
144+
// Should match toMercator result
145+
final directMercator = toMercator(wgs84);
146+
expect(mercator[0], equals(directMercator[0]));
147+
expect(mercator[1], equals(directMercator[1]));
148+
149+
// Test Mercator to WGS84 conversion
150+
final backToWgs84 = convertCoordinates(
151+
mercator,
152+
CoordinateSystem.mercator,
153+
CoordinateSystem.wgs84
154+
);
155+
156+
// Should match toWGS84 result and be close to original
157+
expect(backToWgs84[0], closeTo(wgs84[0], 0.00001));
158+
expect(backToWgs84[1], closeTo(wgs84[1], 0.00001));
159+
160+
// Test same system conversion (should return same values)
161+
final sameSystem = convertCoordinates(
162+
wgs84,
163+
CoordinateSystem.wgs84,
164+
CoordinateSystem.wgs84
165+
);
166+
expect(sameSystem[0], equals(wgs84[0]));
167+
expect(sameSystem[1], equals(wgs84[1]));
168+
169+
// Test error case
170+
expect(
171+
() => convertCoordinates([], CoordinateSystem.wgs84, CoordinateSystem.mercator),
172+
throwsException
173+
);
174+
});
80175
}

0 commit comments

Comments
 (0)