2017-02-03 27 views
3

Не знаете, как решить эту проблему. У меня есть 2 колонки по географииSQL Server space найти оставшееся расстояние

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, к которому это расстояние рассчитывается. Тогда мне нужно каким-то образом вычислить расстояние от этой точки до END полилинии.

Real Описание мира:

Автомобиль может быть от маршрута (всегда будет). Но мне нужно найти ближайшую точку на маршруте и сколько осталось этой дистанции.

ответ

0

Хорошо, я решил это. Не стесняйтесь комментировать и предлагать лучший/быстрый способ. После некоторых исследований кажется, что нет возможности сделать это со встроенной функциональностью SQL Server. Поэтому я прибегал к использованию интеграции CLR.

Существует куча «допущений», и решение не является полностью точным с точки зрения математики. Но моей целью была скорость. Итак, я сделал то, что сделал. Кроме того, в нашей БД мы храним GPS и маршрутизируем как Lat/Lon и строки (те строки, которые похожи на то, что возвращает несколько веб-сервисов, только серия точек)

Просто посмотрев, какие у нас данные - у нас никогда не бывает индивидуальных «ног» «более 1/2-1 мили. И я считаю, что ошибка составляет всего 0,2 мили. Что для того, что мы делаем (оценка оставшегося расстояния/времени для дорожных грузовиков) - ничто. Производительность очень приличная, сравнивая наши попытки с типами географии. Но если у вас есть предложения по улучшению скорости кода 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; } 
    } 
}