После того, как я опубликовал свой первый ответ с помощью STBuffer, я понял, что существует более простой способ получить угловые точки на определенном расстоянии от точки.
Я сохраняю исходный ответ STBuffer, поскольку он не ошибается, и он может быть полезен в качестве примера использования STBuffer и итерации точек в объекте Geography.
Вот более простая реализация GetBorderBounds:
public static void GetBorderBounds2(SqlGeography point, double distanceMeters, out double minLat, out double minLong, out double maxLat, out double maxLong)
{
var metresPerDegreeLat = point.STDistance(SqlGeography.Point((double)point.Lat + 1.0, (double)point.Long, 4326));
var metresPerDegreeLong = point.STDistance(SqlGeography.Point((double)point.Lat, (double)point.Long + 1.0, 4326));
minLat = (double)(point.Lat - distanceMeters / metresPerDegreeLat);
maxLat = (double)(point.Lat + distanceMeters / metresPerDegreeLat);
minLong = (double)(point.Long - distanceMeters / metresPerDegreeLong);
maxLong = (double)(point.Long + distanceMeters / metresPerDegreeLong);
}
Эта реализация ломается вблизи Северного и Южного полюсов.Также, как и в случае с первым решением, оно дает неправильный ответ, если граница пересекает 180-й меридиан.
Если все ваши точки находятся вдали от полюсов или 180-го меридиана, это не имеет значения.
Если ваши точки могут быть рядом с полюсами или 180-м меридианом, вот пуленепробиваемая реализация:
public static void GetBorderBounds3(SqlGeography point, double distanceMeters, out double minLat, out double minLong, out double maxLat, out double maxLong)
{
// Near the North pole:
// Select whole circle of longitude from North pole to latitude south of point by distance
if (point.Lat >= 89)
{
minLat = (double)(point.Lat - distanceMeters / point.STDistance(SqlGeography.Point((double)point.Lat - 1.0, (double)point.Long, 4326)));
maxLat = 90;
minLong = -180;
maxLong = 180;
return;
}
// Near the South pole:
// Select whole circle of longitude from South pole to latitude north of point by distance metres
if (point.Lat <= -89)
{
minLat = -90;
maxLat = (double)(point.Lat + distanceMeters / point.STDistance(SqlGeography.Point((double)point.Lat + 1.0, (double)point.Long, 4326)));
minLong = -180;
maxLong = 180;
return;
}
var metresPerDegreeLat = point.STDistance(SqlGeography.Point((double)point.Lat + 1.0, (double)point.Long, 4326));
var metresPerDegreeLong = point.STDistance(SqlGeography.Point((double)point.Lat, (double)point.Long + 1.0, 4326));
minLat = (double)(point.Lat - distanceMeters / metresPerDegreeLat);
maxLat = (double)(point.Lat + distanceMeters / metresPerDegreeLat);
minLong = (double)(point.Long - distanceMeters / metresPerDegreeLong);
maxLong = (double)(point.Long + distanceMeters / metresPerDegreeLong);
// If we cross the 180th meridian, select the whole circle of longitude:
if (minLong < -180 || maxLong > 180.0)
{
minLong = -180;
maxLong = 180;
}
}