Description
Hello,
First off - thank you for this library! It's helped immensly in a GIS data collection app and has cut down on some of the GIS related code I needed to port over when I migrated to Flutter at my workplace.
The recent addition of ECEF coordinates has been great too. An extension I had to make in my local code is to convert ECEF coordinates into a local tangent plane (usually called east north up in GIS things): https://en.wikipedia.org/wiki/Local_tangent_plane_coordinates#:~:text=It%20consists%20of%20three%20coordinates,%2C%20down%20(NED)%20coordinates.
This is because given a list of polygon latitude longitudes coordinates (a polygon in this case is just a simple one with one set of exterior coordinates) I want to calculate the 2D area of this polygon. You can't just use the shoelace formula on ECEF cartesian coordinates (I'm still trying to fully understand why but my understanding is that ECEF coordinates are not planar and will warp). So you usually convert the ECEF coordinates into a local tangent plane space (where 0,0 of the plane is first coordinate of the polygon) and then the usual area calculations work. This will only work well for lengths of like less than 250km (any area of the earth you can approximate accurately to a plane) - for larger ones you get into the realm of using UTM coordinate systems.
I thought I'd pass this over anyways as it may be useful and might be something you want to add to the library :)
static double areaTangentPlane(gb.Geometry geometry) {
final enuPositions = <gb.Position>[];
final positions = getGeometryPositions(geometry);
if (positions.isEmpty || positions.length < 3) {
return 0;
}
final referenceGeo = gb.Geographic(lon: positions.first.x, lat: positions.first.y, elev: positions.first.z);
final referenceECEF = referenceGeo.toGeocentricCartesian();
for (var pos in positions) {
final geo = gb.Geographic(lon: pos.x, lat: pos.y, elev: pos.z);
final ecef = geo.toGeocentricCartesian();
final enu = ENU.fromECEF(ecef, referenceECEF, LatLng(referenceGeo.lat, referenceGeo.lon));
enuPositions.add(gb.Position.create(x: enu.east, y: enu.north));
}
final area = gb.PositionSeries.from(enuPositions, type: gb.Coords.xyz).signedArea2D().abs();
return area;
}
class ENU {
final double east;
final double north;
final double up;
ENU(this.east, this.north, this.up);
factory ENU.fromECEF(gb.Position ecef, gb.Position reference, LatLng referenceLatLng) {
double dx = ecef.x - reference.x;
double dy = ecef.y - reference.y;
double dz = ecef.z - reference.z;
double lat0 = referenceLatLng.latitude * pi / 180.0; // Convert to radians
double lon0 = referenceLatLng.longitude * pi / 180.0;
double sinLat = sin(lat0), cosLat = cos(lat0);
double sinLon = sin(lon0), cosLon = cos(lon0);
// Convert to local ENU coordinates
double east = -sinLon * dx + cosLon * dy;
double north = -sinLat * cosLon * dx - sinLat * sinLon * dy + cosLat * dz;
double up = cosLat * cosLon * dx + cosLat * sinLon * dy + sinLat * dz;
return ENU(east, north, up);
}
}