SQL 服务器空间查找剩余距离
SQL Server spatial find remaining distance
不知道如何解决这个问题。我有 2 个地理列
CREATE TABLE #Trip
...
LegRoute geography NULL,
GPSPoint geography NULL,
GPSPointToLegRouteDistance Float NULL,
...
LegRoute
包含折线
GPSPoint
包含点
我可以获得点到线的距离(以英里为单位)。这是相对于路径的 GPS 位置。
UPDATE T
SET GPSPointToLegRouteDistance = LegRoute.STDistance(GPSPoint) / 1609.344
FROM #Trip T
我需要找到的是这个距离计算到的POINT
。然后我需要一些方法来计算从该点到折线末端的距离。
真实世界描述:
车辆可能会偏离路线(总是会)。但是我需要找到路线上最近的点以及剩余的行程距离。
好的,我确实解决了这个问题。欢迎发表评论并提出 better/faster 的建议。经过一些研究,似乎没有办法用 built-in SQL 服务器功能来做到这一点。所以我求助于使用 CLR 集成。
有一堆 "assumptions" 并且从数学角度来看解决方案并不完全准确。但我的目标是速度。所以,我做了我所做的。此外,在我们的数据库中 - 我们将 GPS 和路线存储为 Lat/Lon 和字符串(这些字符串类似于多个网络服务 return,只是一系列点)
只要看看我们有什么数据——我们从来没有 "legs" 超过 1/2-1 英里的人。我估计误差最多在 0.2 英里以内。这对于我们所做的事情(估计公路卡车的剩余驾驶 distance/time)没什么。与我们对地理类型的尝试相比,性能非常不错。但是,如果您有关于提高 C# 代码速度的建议 - 请采纳..
public class Functions
{
/// <summary>
/// Function receives path and current location. We find remaining distance after
/// matching position. Function will return null in case of error.
/// If everything goes well but last point on route is closest to GPS - we return 0
/// </summary>
/// <param name="lat">
/// Latitude of current location
/// </param>
/// <param name="lon">
/// Longitude of current location
/// </param>
/// <param name="path">
/// Path as a series of points just like we have it in RouteCache
/// </param>
/// <param name="outOfRouteMetersThreshhold">
/// Out of route distance we can tolerate. If reached - return NULL
/// </param>
/// <returns>
/// Meters to end of path.
/// </returns>
[SqlFunction]
public static double? RemainingDistance(double lat, double lon, string path, double outOfRouteThreshhold)
{
var gpsPoint = new Point { Lat = lat, Lon = lon };
// Parse path into array of points
// Loop and find point in sequence closest to our input GPS lat/lon
// IMPORTANT!!! There is some simplification of issue here.
// We assume that linestring is pretty granular with small-ish segments
// This way we don't care if distance is to segment. We just check distance to each point.
// This will give better performance but will not be too precise. For what we do - it's OK
var closestPointIndex = 0;
double distance = 10000;
var pointArrayStr = path.Split(',');
if (pointArrayStr.Length < 2) return null;
for (var i = 0; i < pointArrayStr.Length; i++)
{
var latLonStr = pointArrayStr[i].Split(' ');
var currentDistance = DistanceSqrt(
gpsPoint,
new Point { Lat = double.Parse(latLonStr[1]), Lon = double.Parse(latLonStr[0]) });
if (currentDistance >= distance) continue;
distance = currentDistance;
closestPointIndex = i;
}
// Closest point known. Let's see what this distance in meters and handle out of route
var closestPointStr = pointArrayStr[closestPointIndex].Split(' ');
var closestPoint = new Point { Lat = double.Parse(closestPointStr[1]), Lon = double.Parse(closestPointStr[0]) };
var distanceInMeters = DistanceMeters(gpsPoint, closestPoint);
if (distanceInMeters > outOfRouteThreshhold) return null;
// Last point closest, this is "complete" route or something wrong with passed data
if (closestPointIndex == pointArrayStr.Length - 1) return 0;
// Reconstruct path string, but only for remaining points in line
var strBuilder = new StringBuilder();
for (var i = closestPointIndex; i < pointArrayStr.Length; i++) strBuilder.Append(pointArrayStr[i] + ",");
strBuilder.Remove(strBuilder.Length - 1, 1);
// Create geography linestring and calculate lenght. This will be our remaining driving distance
try
{
var geoPath = SqlGeography.STGeomFromText(new SqlChars($"LINESTRING({strBuilder})"), 4326);
var dist = geoPath.STLength().Value;
return dist;
}
catch (Exception)
{
return -1;
}
}
// Compute the distance from A to B
private static double DistanceSqrt(Point pointA, Point pointB)
{
var d1 = pointA.Lat - pointB.Lat;
var d2 = pointA.Lon - pointB.Lon;
return Math.Sqrt(d1 * d1 + d2 * d2);
}
private static double DistanceMeters(Point pointA, Point pointB)
{
var e = Math.PI * pointA.Lat / 180;
var f = Math.PI * pointA.Lon / 180;
var g = Math.PI * pointB.Lat / 180;
var h = Math.PI * pointB.Lon / 180;
var i = Math.Cos(e) * Math.Cos(g) * Math.Cos(f) * Math.Cos(h)
+ Math.Cos(e) * Math.Sin(f) * Math.Cos(g) * Math.Sin(h)
+ Math.Sin(e) * Math.Sin(g);
var j = Math.Acos(i);
var k = 6371 * j; // 6371 earth radius
return k * 1000;
}
private struct Point
{
public double Lat { get; set; }
public double Lon { get; set; }
}
}
不知道如何解决这个问题。我有 2 个地理列
CREATE TABLE #Trip
...
LegRoute geography NULL,
GPSPoint geography NULL,
GPSPointToLegRouteDistance Float NULL,
...
LegRoute
包含折线GPSPoint
包含点
我可以获得点到线的距离(以英里为单位)。这是相对于路径的 GPS 位置。
UPDATE T
SET GPSPointToLegRouteDistance = LegRoute.STDistance(GPSPoint) / 1609.344
FROM #Trip T
我需要找到的是这个距离计算到的POINT
。然后我需要一些方法来计算从该点到折线末端的距离。
真实世界描述:
车辆可能会偏离路线(总是会)。但是我需要找到路线上最近的点以及剩余的行程距离。
好的,我确实解决了这个问题。欢迎发表评论并提出 better/faster 的建议。经过一些研究,似乎没有办法用 built-in SQL 服务器功能来做到这一点。所以我求助于使用 CLR 集成。
有一堆 "assumptions" 并且从数学角度来看解决方案并不完全准确。但我的目标是速度。所以,我做了我所做的。此外,在我们的数据库中 - 我们将 GPS 和路线存储为 Lat/Lon 和字符串(这些字符串类似于多个网络服务 return,只是一系列点)
只要看看我们有什么数据——我们从来没有 "legs" 超过 1/2-1 英里的人。我估计误差最多在 0.2 英里以内。这对于我们所做的事情(估计公路卡车的剩余驾驶 distance/time)没什么。与我们对地理类型的尝试相比,性能非常不错。但是,如果您有关于提高 C# 代码速度的建议 - 请采纳..
public class Functions
{
/// <summary>
/// Function receives path and current location. We find remaining distance after
/// matching position. Function will return null in case of error.
/// If everything goes well but last point on route is closest to GPS - we return 0
/// </summary>
/// <param name="lat">
/// Latitude of current location
/// </param>
/// <param name="lon">
/// Longitude of current location
/// </param>
/// <param name="path">
/// Path as a series of points just like we have it in RouteCache
/// </param>
/// <param name="outOfRouteMetersThreshhold">
/// Out of route distance we can tolerate. If reached - return NULL
/// </param>
/// <returns>
/// Meters to end of path.
/// </returns>
[SqlFunction]
public static double? RemainingDistance(double lat, double lon, string path, double outOfRouteThreshhold)
{
var gpsPoint = new Point { Lat = lat, Lon = lon };
// Parse path into array of points
// Loop and find point in sequence closest to our input GPS lat/lon
// IMPORTANT!!! There is some simplification of issue here.
// We assume that linestring is pretty granular with small-ish segments
// This way we don't care if distance is to segment. We just check distance to each point.
// This will give better performance but will not be too precise. For what we do - it's OK
var closestPointIndex = 0;
double distance = 10000;
var pointArrayStr = path.Split(',');
if (pointArrayStr.Length < 2) return null;
for (var i = 0; i < pointArrayStr.Length; i++)
{
var latLonStr = pointArrayStr[i].Split(' ');
var currentDistance = DistanceSqrt(
gpsPoint,
new Point { Lat = double.Parse(latLonStr[1]), Lon = double.Parse(latLonStr[0]) });
if (currentDistance >= distance) continue;
distance = currentDistance;
closestPointIndex = i;
}
// Closest point known. Let's see what this distance in meters and handle out of route
var closestPointStr = pointArrayStr[closestPointIndex].Split(' ');
var closestPoint = new Point { Lat = double.Parse(closestPointStr[1]), Lon = double.Parse(closestPointStr[0]) };
var distanceInMeters = DistanceMeters(gpsPoint, closestPoint);
if (distanceInMeters > outOfRouteThreshhold) return null;
// Last point closest, this is "complete" route or something wrong with passed data
if (closestPointIndex == pointArrayStr.Length - 1) return 0;
// Reconstruct path string, but only for remaining points in line
var strBuilder = new StringBuilder();
for (var i = closestPointIndex; i < pointArrayStr.Length; i++) strBuilder.Append(pointArrayStr[i] + ",");
strBuilder.Remove(strBuilder.Length - 1, 1);
// Create geography linestring and calculate lenght. This will be our remaining driving distance
try
{
var geoPath = SqlGeography.STGeomFromText(new SqlChars($"LINESTRING({strBuilder})"), 4326);
var dist = geoPath.STLength().Value;
return dist;
}
catch (Exception)
{
return -1;
}
}
// Compute the distance from A to B
private static double DistanceSqrt(Point pointA, Point pointB)
{
var d1 = pointA.Lat - pointB.Lat;
var d2 = pointA.Lon - pointB.Lon;
return Math.Sqrt(d1 * d1 + d2 * d2);
}
private static double DistanceMeters(Point pointA, Point pointB)
{
var e = Math.PI * pointA.Lat / 180;
var f = Math.PI * pointA.Lon / 180;
var g = Math.PI * pointB.Lat / 180;
var h = Math.PI * pointB.Lon / 180;
var i = Math.Cos(e) * Math.Cos(g) * Math.Cos(f) * Math.Cos(h)
+ Math.Cos(e) * Math.Sin(f) * Math.Cos(g) * Math.Sin(h)
+ Math.Sin(e) * Math.Sin(g);
var j = Math.Acos(i);
var k = 6371 * j; // 6371 earth radius
return k * 1000;
}
private struct Point
{
public double Lat { get; set; }
public double Lon { get; set; }
}
}