3

我尝试了用于合并要导入 Google 地球的多边形 (c#) 的剪裁器库。它适用于两个圆的平面样本,但使用经度和纬度作为 X 和 Y 存在两个问题:

  1. Clipper 仅支持 long as 数据类型(您可以在将值传递给 Clipper 之前使用乘数来解决这个问题 - 已经有一个问题,但它没有解决第二点
  2. Clipper 假设表面是平坦的,因此输出坐标不在预期的位置。不可能只将 X 和 Y 视为经度和纬度并将值 x 添加到它们。它可能在赤道起作用,但在较高的北方将相同的经度值添加到会导致更短的距离。

在此处输入图像描述

我想知道是否已经有该场景的实现/库。

更新:由于已请求使用的源代码,我创建了一个包含相关代码的小示例应用程序:

internal class Polygon : List<DoublePoint> { }
internal class Polygons : List<List<DoublePoint>> { }

internal class IntPolygon : List<IntPoint> { }
internal class IntPolygons : List<List<IntPoint>> { }

class Program
{
    static void Main(string[] args)
    {
        MercatorProjection mercatorProjection = new MercatorProjection();

        List<GeoCoordinate> circle1Coordinates = GenerateCirclePolygonByMeters(new GeoCoordinate { Longitude = 50, Latitude = 50 }, 5000);
        List<GeoCoordinate> circle2Coordinates = GenerateCirclePolygonByMeters(new GeoCoordinate { Longitude = 50.05, Latitude = 50 }, 5000);

        Polygons polygons = new Polygons();

        Polygon circle1 = new Polygon();
        foreach(var coordinate in circle1Coordinates)
        {
            DoublePoint point = mercatorProjection.FromLatLngToPoint(coordinate.Latitude, coordinate.Longitude, 15);
            circle1.Add(point);
        }

        Polygon circle2 = new Polygon();
        foreach (var coordinate in circle2Coordinates)
        {
            DoublePoint point = mercatorProjection.FromLatLngToPoint(coordinate.Latitude, coordinate.Longitude, 15);
            circle2.Add(point);
        }

        polygons.Add(circle1);
        polygons.Add(circle2);

        // Workaround: get int-values
        IntPolygons intPolygons = ConvertToIntPolygons(polygons);

        // Merge
        IntPolygons mergedIntPolygons = MergePolygons(intPolygons);

        // Workaroud: Get doubles again
        Polygons mergedPolygons = ConvertToDoublePolygons(mergedIntPolygons);

        // Convert back to spherical surface
        // GeoCoordinate class is from System.Device
        List<GeoCoordinate> mergedCoordinates = new List<GeoCoordinate>();
        foreach (var polygon in mergedPolygons)
        {
            foreach (var point in polygon)
            {
                GeoCoordinate coordinate = mercatorProjection.FromPointToLatLng(point, 15);
                mergedCoordinates.Add(coordinate);
            }
        }

        // Generate output csv-list
        WriteOutputFile(mergedCoordinates);
    }

    private static void WriteOutputFile(List<GeoCoordinate> coordinates, string filename = "")
    {
        string uniquename = DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".txt";
        string filenameToUse = String.IsNullOrEmpty(filename) ? uniquename : filename;
        var fileStream = File.OpenWrite(filenameToUse);

        using (var writer = new StreamWriter(fileStream))
        {
            // header
            writer.WriteLine("lat,long");

            // content
            foreach (var coordinate in coordinates)
            {
                writer.WriteLine(coordinate.Latitude.ToString(CultureInfo.InvariantCulture) + "," + coordinate.Longitude.ToString(CultureInfo.InvariantCulture));
            }

            writer.Close();
        }
    }

    private static Polygons ConvertToDoublePolygons(IntPolygons polygons)
    {
        double multiplier = 100;

        Polygons doublePolygons = new Polygons();
        foreach (var intPolygon in polygons)
        {
            Polygon doublePolygon = new Polygon();
            foreach (var intPoint in intPolygon)
            {
                doublePolygon.Add(new DoublePoint { X = intPoint.X / multiplier, Y = intPoint.Y / multiplier });
            }

            doublePolygons.Add(doublePolygon);
        }

        return doublePolygons;
    }

    private static IntPolygons ConvertToIntPolygons(Polygons polygons)
    {
        double multiplier = 100;
        IntPolygons intPolygons = new IntPolygons();
        foreach (var doublePolygon in polygons)
        {
            IntPolygon intPolygon = new IntPolygon();
            foreach (var doublePoint in doublePolygon)
            {
                intPolygon.Add(new IntPoint { X = (int)(doublePoint.X * multiplier), Y = (int)(doublePoint.Y * multiplier) });
            }

            intPolygons.Add(intPolygon);
        }

        return intPolygons;
    }

    private static List<GeoCoordinate> GenerateCirclePolygonByMeters(GeoCoordinate position, double distanceMeters)
    {
        // calc distance in coordinates-system
        double latitude2 = CalculateDerivedPosition(position, distanceMeters, 0).Latitude;
        double yRadius = latitude2 - position.Latitude;
        double longitude2 = CalculateDerivedPosition(position, distanceMeters, 90).Longitude;
        double xRadius = longitude2 - position.Longitude;

        var circle = GenerateCirclePolygon(position.Longitude, position.Latitude, xRadius, yRadius, 16);

        List<GeoCoordinate> circleCoordinates = new List<GeoCoordinate>();
        foreach(var point in circle)
        {
            circleCoordinates.Add(new GeoCoordinate { Latitude = point.Y, Longitude = point.X });
        }

        return circleCoordinates;
    }

    private static Polygon GenerateCirclePolygon(double xStart, double yStart, double xRadius, double yRadius, int points)
    {
        Polygon polygon = new Polygon();
        double slice = 2 * Math.PI / points;


        for (int i = 0; i < points; i++)
        {
            double angle = slice * i;
            Console.WriteLine(angle);
            double x = xRadius * Math.Cos(angle);
            double y = yRadius * Math.Sin(angle);

            polygon.Add(new DoublePoint(xStart + x, yStart + y));
        }

        return polygon;
    }

    // Source: https://stackoverflow.com/questions/1125144/how-do-i-find-the-lat-long-that-is-x-km-north-of-a-given-lat-long
    public static GeoCoordinate CalculateDerivedPosition(GeoCoordinate source, double rangeMeters, double bearing)
    {
        double radiansToDegrees = 57.2957795;
        double degreesToRadians = 0.0174532925;
        double twoPi = Math.PI * 2;
        double earthRadius = 6378137.0;

        double latA = source.Latitude * degreesToRadians;
        double lonA = source.Longitude * degreesToRadians;
        double angularDistance = rangeMeters / earthRadius;
        double trueCourse = bearing * degreesToRadians;

        double lat = Math.Asin(
            Math.Sin(latA) * Math.Cos(angularDistance) +
            Math.Cos(latA) * Math.Sin(angularDistance) * Math.Cos(trueCourse));

        double dlon = Math.Atan2(
            Math.Sin(trueCourse) * Math.Sin(angularDistance) * Math.Cos(latA),
            Math.Cos(angularDistance) - Math.Sin(latA) * Math.Sin(lat));

        double lon = ((lonA + dlon + Math.PI) % twoPi) - Math.PI;

        return new GeoCoordinate(
            lat * radiansToDegrees,
            lon * radiansToDegrees);
    }

    private static IntPolygons MergePolygons(IntPolygons polygons)
    {
        Clipper clipper = new Clipper();

        clipper.AddPaths(polygons, PolyType.ptSubject, true);

        IntPolygons mergedPolygons = new IntPolygons();
        clipper.Execute(ClipType.ctUnion, mergedPolygons,
            PolyFillType.pftNonZero, PolyFillType.pftNonZero);

        return mergedPolygons;
    }

更新 2:圈子的创建方式实际上存在错误。通过更正,圆圈得到正确创建和合并: 在此处输入图像描述

我发现我可以使用墨卡托投影将上图中的圆点投影到平面上。然后可以使用 Clipper 进行合并。最后,所有点都再次投影到球体上(墨卡托投影的反面)。

这将是一种解决方法(将对其进行更深入的测试)。所以这个问题已经得到了回答,但如果有其他解决方案较少的解决方法会很有趣。

4

0 回答 0