Esempio n. 1
0
        /// <summary>
        /// Internal: Create a LinkedGeoPolygon from a vertex graph. It is the
        /// responsibility of the caller to call destroyLinkedPolygon on the populated
        /// linked geo structure, or the memory for that structure will not be freed.
        /// </summary>
        /// <param name="graph">Input graph</param>
        /// <returns>Output polygon</returns>
        /// <!--
        /// algos.c
        /// void _vertexGraphToLinkedGeo
        /// -->
        public static LinkedGeoPolygon ToLinkedGeoPolygon(this VertexGraph graph)
        {
            var result = new LinkedGeoPolygon();

            while (graph.Count > 0)
            {
                var edge = graph.FirstNode();
                var loop = new LinkedGeoLoop();
                while (edge.HasValue)
                {
                    loop.AddLinkedCoord(edge.Value.From);
                    //loop.GeoCoordList.AddLast(edge.Value.From);
                    var nextVertex = edge.Value.To;
                    graph.RemoveNode(edge.Value);
                    edge = graph.FindVertex(nextVertex);
                }

                if (loop.Count > 0)
                {
                    result.AddLinkedLoop(loop);
                }
            }

            return(result);
        }
Esempio n. 2
0
        /// <summary>
        /// Free all allocated memory for a linked geo structure. The caller is
        /// responsible for freeing memory allocated to input polygon struct.
        /// </summary>
        /// <param name="polygon">Pointer to the first polygon in the structure</param>
        /// <!-- Based off 3.1.1 -->
        public static void destroyLinkedPolygon(ref LinkedGeoPolygon polygon)
        {
            // flag to skip the input polygon
            bool             skip = true;
            LinkedGeoPolygon nextPolygon;
            LinkedGeoLoop    nextLoop;

            for (LinkedGeoPolygon currentPolygon = polygon; currentPolygon != null;
                 currentPolygon = nextPolygon)
            {
                for (LinkedGeoLoop currentLoop = currentPolygon.first;
                     currentLoop != null; currentLoop = nextLoop)
                {
                    destroyLinkedGeoLoop(ref currentLoop);
                    nextLoop = currentLoop.next;
                    // ReSharper disable once RedundantAssignment
                    currentLoop = null;
                }
                nextPolygon = currentPolygon.next;
                if (skip)
                {
                    // do not free the input polygon
                    skip = false;
                }
                else
                {
                    // ReSharper disable once RedundantAssignment
                    currentPolygon = null;
                }
            }
        }
Esempio n. 3
0
 /// <summary>
 /// Count the number of polygons containing a given loop.
 /// </summary>
 /// <param name="loop">Loop to count containers for</param>
 /// <param name="polygons">Polygons to test</param>
 /// <param name="boxes">Bounding boxes for polygons, used in point-in-poly check</param>
 /// <returns>Number of polygons containing the loop</returns>
 /// <!--
 /// linkedGeo.c
 /// static int countContainers
 /// -->
 public static int CountContainers(this LinkedGeoLoop loop, List <LinkedGeoPolygon> polygons, List <BBox> boxes)
 {
     return(polygons
            .Where((t, i) =>
                   loop != t.First &&
                   t.First.PointInside(boxes[i], loop.First.Vertex))
            .Count());
 }
Esempio n. 4
0
        /// <summary>
        /// Add a new linked loop to the current polygon
        /// </summary>
        /// <param name="polygon">Polygon to add loop to</param>
        /// <returns>Pointer to loop</returns>
        /// <!-- Based off 3.1.1 -->
        public static LinkedGeoLoop addNewLinkedLoop(ref LinkedGeoPolygon polygon)
        {
            LinkedGeoLoop loop = new LinkedGeoLoop();

            if (loop == null)
            {
                throw new Exception("FAIL: assert(loop != NULL)");
            }

            return(addLinkedLoop(ref polygon, ref loop));
        }
Esempio n. 5
0
        private static LinkedGeoLoop CreateLinkedLoop(IEnumerable <GeoCoord> verts)
        {
            var loop = new LinkedGeoLoop();

            foreach (var geoCoord in verts)
            {
                loop.AddLinkedCoord(geoCoord);
            }

            return(loop);
        }
Esempio n. 6
0
        public void BboxFromLinkedGeoLoopNoVertices()
        {
            var loop     = new LinkedGeoLoop();
            var expected = new BBox(0.0m, 0.0m, 0.0m, 0.0m);

            var result = loop.ToBBox();

            Assert.AreEqual(result, expected);

            loop.Clear();
        }
Esempio n. 7
0
        /// <summary>
        /// Find the polygon to which a given hole should be allocated. Note that this
        /// function will return null if no parent is found.
        /// </summary>
        /// <param name="loop">Inner loop describing a hole</param>
        /// <param name="polygon">Head of a linked list of polygons to check</param>
        /// <param name="bboxes">Bounding boxes for polygons, used in point-in-poly check</param>
        /// <param name="polygonCount">Number of polygons to check</param>
        /// <returns>Pointer to parent polygon, or null if not found</returns>
        /// <!-- Based off 3.1.1 -->
        public static LinkedGeoPolygon findPolygonForHole(
            ref LinkedGeoLoop loop,
            ref LinkedGeoPolygon polygon,
            ref List <BBox> bboxes,
            int polygonCount)
        {
            // Early exit with no polygons
            if (polygonCount == 0)
            {
                return(null);
            }
            // Initialize arrays for candidate loops and their bounding boxes
            List <LinkedGeoPolygon> candidates      = new List <LinkedGeoPolygon>(polygonCount);
            List <BBox>             candidateBBoxes = new List <BBox>(polygonCount);

            for (var k = 0; k < polygonCount; k++)
            {
                candidates.Add(new LinkedGeoPolygon());
                candidateBBoxes.Add(new BBox());
            }

            // Find all polygons that contain the loop
            int candidateCount   = 0;
            int index            = 0;
            var polygonReference = polygon;

            while (polygonReference != null)
            {
                // We are guaranteed not to overlap, so just test the first point
                var bb = bboxes[index];
                if (
                    pointInsideLinkedGeoLoop(
                        ref polygonReference.first, ref bb, ref loop.first.vertex
                        )
                    )
                {
                    candidates[candidateCount]      = polygonReference;
                    candidateBBoxes[candidateCount] = bboxes[index];
                    candidateCount++;
                }
                polygonReference = polygonReference.next;
                index++;
            }

            // The most deeply nested container is the immediate parent
            LinkedGeoPolygon parent =
                findDeepestContainer(ref candidates, ref candidateBBoxes, candidateCount);

            // Free allocated memory
            candidates      = null;
            candidateBBoxes = null;
            return(parent);
        }
Esempio n. 8
0
        /// <summary>
        /// Free all allocated memory for a linked geo loop. The caller is
        /// responsible for freeing memory allocated to input loop struct.
        /// </summary>
        /// <param name="loop">Loop to free</param>
        /// <!-- Based off 3.1.1 -->
        public static void destroyLinkedGeoLoop(ref LinkedGeoLoop loop)
        {
            LinkedGeoCoord nextCoord;

            for (LinkedGeoCoord currentCoord = loop.first; currentCoord != null;
                 currentCoord = nextCoord)
            {
                nextCoord = currentCoord.next;
                // ReSharper disable once RedundantAssignment
                currentCoord = null;
            }
        }
Esempio n. 9
0
        /// <summary>
        /// Count the number of coordinates in a loop
        /// </summary>
        /// <param name="loop"> Loop to count coordinates for</param>
        /// <returns>Count</returns>
        /// <!-- Based off 3.1.1 -->
        public static int countLinkedCoords(ref LinkedGeoLoop loop)
        {
            LinkedGeoCoord coord = loop.first;
            int            count = 0;

            while (coord != null)
            {
                count++;
                coord = coord.next;
            }
            return(count);
        }
Esempio n. 10
0
        /// <summary>
        /// Count the number of linked loops in a polygon
        /// </summary>
        /// <param name="polygon">Polygon to count loops for</param>
        /// <returns>Count</returns>
        /// <!-- Based off 3.1.1 -->
        public static int countLinkedLoops(ref LinkedGeoPolygon polygon)
        {
            LinkedGeoLoop loop  = polygon.first;
            int           count = 0;

            while (loop != null)
            {
                count++;
                loop = loop.next;
            }
            return(count);
        }
Esempio n. 11
0
 public LinkedGeoPolygon(Code.LinkedGeo.LinkedGeoPolygon codeLinkedGeoPolygon)
 {
     if (codeLinkedGeoPolygon.first != null)
     {
         First = new LinkedGeoLoop(codeLinkedGeoPolygon.first);
     }
     if (codeLinkedGeoPolygon.last != null)
     {
         Last = new LinkedGeoLoop(codeLinkedGeoPolygon.last);
     }
     if (codeLinkedGeoPolygon.next != null)
     {
         Next = new LinkedGeoPolygon(codeLinkedGeoPolygon.next);
     }
 }
Esempio n. 12
0
 public LinkedGeoLoop(Code.LinkedGeo.LinkedGeoLoop codeLinkedGeoLoop)
 {
     if (codeLinkedGeoLoop.first != null)
     {
         First = new LinkedGeoCoord(codeLinkedGeoLoop.first);
     }
     if (codeLinkedGeoLoop.last != null)
     {
         Last = new LinkedGeoCoord(codeLinkedGeoLoop.last);
     }
     if (codeLinkedGeoLoop.next != null)
     {
         Next = new LinkedGeoLoop(codeLinkedGeoLoop.next);
     }
 }
Esempio n. 13
0
        /// <summary>
        /// Count the number of polygons containing a given loop.
        /// </summary>
        /// <param name="loop">Loop to count containers for</param>
        /// <param name="polygons">Polygons to test</param>
        /// <param name="bboxes">Bounding boxes for polygons, used in point-in-poly check</param>
        /// <param name="polygonCount">Number of polygons in the test array</param>
        /// <returns>Number of polygons containing the loop
        /// <!-- Based off 3.1.1 -->
        public static int countContainers(
            LinkedGeoLoop loop, List <LinkedGeoPolygon> polygons,
            List <BBox> bboxes, int polygonCount)
        {
            int containerCount = 0;

            for (int i = 0; i < polygonCount; i++)
            {
                var bb = bboxes[i];
                if (loop != polygons[i].first &&
                    pointInsideLinkedGeoLoop(ref polygons[i].first, ref bb, ref loop.first.vertex))
                {
                    containerCount++;
                }
            }
            return(containerCount);
        }
Esempio n. 14
0
        /// <summary>
        /// Add an existing linked loop to the current polygon
        /// </summary>
        /// <param name="polygon">Polygon to add loop to</param>
        /// <returns>Pointer to loop</returns>
        /// <!-- Based off 3.1.1 -->
        public static LinkedGeoLoop addLinkedLoop(ref LinkedGeoPolygon polygon, ref LinkedGeoLoop loop)
        {
            LinkedGeoLoop last = polygon.last;

            if (last == null)
            {
                if (polygon.first != null)
                {
                    throw new Exception("FAIL: assert(polygon->first == NULL)");
                }
                polygon.first = loop;
            }
            else
            {
                last.next = loop;
            }
            polygon.last = loop;
            return(loop);
        }
        /// <summary>
        /// Find the polygon to which a given hole should be allocated. Note that this
        /// function will return null if no parent is found.
        /// </summary>
        /// <param name="loop">Inner loop describing a hole</param>
        /// <param name="polygon">Head of a linked list of polygons to check</param>
        /// <param name="boxes">Bounding boxes for polygons, used in point-in-poly check</param>
        /// <param name="polygonCount">Number of polygons to check</param>
        /// <returns>Pointer to parent polygon, or null if not found</returns>
        /// <!--
        /// linkedGeo.c
        /// static const LinkedGeoPolygon* findPolygonForHole
        /// -->
        private static LinkedGeoPolygon FindPolygonForHole(
            this LinkedGeoLoop loop, LinkedGeoPolygon polygon, List <BBox> boxes,
            int polygonCount
            )
        {
            // Early exit with no polygons
            if (polygonCount == 0)
            {
                return(null);
            }
            // Initialize arrays for candidate loops and their bounding boxes
            var candidates     = new List <LinkedGeoPolygon>();
            var candidateBoxes = new List <BBox>();

            // Find all polygons that contain the loop
            var index = 0;

            while (polygon != null)
            {
                // We are guaranteed not to overlap, so just test the first point
                if (polygon.Loops != null && loop.Nodes != null)
                {
                    if (polygon.Loops.First().PointInside(boxes[index], loop.Nodes.First().Vertex))
                    {
                        candidates.Add(polygon);
                        candidateBoxes.Add(boxes[index]);
                    }
                }
                polygon = polygon.Next;
                index++;
            }

            // The most deeply nested container is the immediate parent
            var parent = candidates.FindDeepestContainer(candidateBoxes);

            // Free allocated memory
            candidates.Clear();
            candidateBoxes.Clear();
            return((parent == null || parent.CountLoops == 0)
                       ? null
                       : parent);
        }
Esempio n. 16
0
            public void Clear()
            {
                if (First != null)
                {
                    First.Clear();
                    First = null;
                }

                if (Last != null)
                {
                    Last.Clear();
                    Last = null;
                }

                if (Next != null)
                {
                    Next.Clear();
                    Next = null;
                }
            }
Esempio n. 17
0
            public void Clear()
            {
                if (First != null)
                {
                    First.Clear();
                    First = null;
                }

                if (Last != null)
                {
                    Last.Clear();
                    Last = null;
                }

                if (Next == null)
                {
                    return;
                }
                Next.Clear();
                Next = null;
            }
Esempio n. 18
0
        /// <summary>
        /// Whether the winding order of a given LinkedGeoLoop is clockwise
        /// </summary>
        /// <param name="loop">The loop to check</param>
        /// <returns>Whether the loop is clockwise</returns>
        /// <!-- Based off 3.1.1 -->
        static bool isClockwiseNormalizedLinkedGeoLoop(LinkedGeoLoop loop, bool isTransmeridian)
        {
            double   sum = 0;
            GeoCoord a;
            GeoCoord b;

            LinkedGeoCoord currentCoord = null;
            LinkedGeoCoord nextCoord    = null;

            while (true)
            {
                currentCoord = currentCoord == null
                                   ? loop.first
                                   : currentCoord.next;
                if (currentCoord == null)
                {
                    break;
                }

                a         = currentCoord.vertex;
                nextCoord = currentCoord.next == null
                                ? loop.first
                                : currentCoord.next;
                b = nextCoord.vertex;
                // If we identify a transmeridian arc (> 180 degrees longitude),
                // start over with the transmeridian flag set
                if (!isTransmeridian && Math.Abs(a.lon - b.lon) > Constants.M_PI)
                {
                    return(isClockwiseNormalizedLinkedGeoLoop(loop, true));
                }
                sum += ((NORMALIZE_LON(b.lon, isTransmeridian) -
                         NORMALIZE_LON(a.lon, isTransmeridian)) *
                        (b.lat + a.lat));
            }

            return(sum > 0);
        }
Esempio n. 19
0
        /// <summary>
        /// Add a new linked coordinate to the current loop
        /// </summary>
        /// <param name="loop">Loop to add coordinate to</param>
        /// <param name="vertex">Coordinate to add</param>
        /// <returns>Pointer to the coordinate</returns>
        /// <!-- Based off 3.1.1 -->
        public static LinkedGeoCoord addLinkedCoord(ref LinkedGeoLoop loop, ref GeoCoord vertex)
        {
            LinkedGeoCoord coord = new LinkedGeoCoord();

            coord.vertex = new GeoCoord(vertex.lat, vertex.lon);
            coord.next   = null;

            LinkedGeoCoord last = loop.last;

            if (last == null)
            {
                if (loop.first != null)
                {
                    throw new Exception("assert(loop->first == NULL);");
                }
                loop.first = coord;
            }
            else
            {
                last.next = coord;
            }
            loop.last = coord;
            return(coord);
        }
Esempio n. 20
0
        /// <summary>
        /// Is loop clockwise normalized?
        /// </summary>
        /// <param name="loop"></param>
        /// <param name="isTransmeridian"></param>
        /// <returns></returns>
        private static bool IsClockwiseNormalized(this LinkedGeoLoop loop, bool isTransmeridian)
        {
            var sum = 0m;

            var nodes = loop.Nodes;

            for (var idx = 0; idx < nodes.Count; idx++)
            {
                var a = nodes[idx];
                var b = nodes[(idx + 1) % nodes.Count];

                // If we identify a transmeridian arc (> 180 degrees longitude),
                // start over with the transmeridian flag set
                if (!isTransmeridian && Math.Abs(a.Longitude - b.Longitude) > Constants.H3.M_PI)
                {
                    return(loop.IsClockwiseNormalized(true));
                }
                sum += (b.Longitude.NormalizeLongitude(isTransmeridian) -
                        a.Longitude.NormalizeLongitude(isTransmeridian)) *
                       (b.Latitude + a.Latitude);
            }

            return(sum > 0);
        }
Esempio n. 21
0
        /// <summary>
        /// Create a bounding box from a simple polygon loop.
        /// Known limitations:
        /// - Does not support polygons with two adjacent points > 180 degrees of
        ///   longitude apart. These will be interpreted as crossing the antimeridian.
        /// - Does not currently support polygons containing a pole.
        /// </summary>
        /// <param name="loop">Loop of coordinates</param>
        /// <param name="bbox">bbox</param>
        /// <!-- Based off 3.1.1 -->
        public static void bboxFromLinkedGeoLoop(ref LinkedGeoLoop loop, ref BBox bbox)
        {
            // Early exit if there are no vertices
            if (loop.first == null)
            {
                bbox = new BBox();
                return;
            }

            bbox.south = Double.MaxValue;
            bbox.west  = Double.MaxValue;
            bbox.north = -Double.MaxValue;
            bbox.east  = -Double.MaxValue;
            double minPosLon       = Double.MaxValue;
            double maxNegLon       = -Double.MaxValue;
            bool   isTransmeridian = false;

            double   lat;
            double   lon;
            GeoCoord coord;
            GeoCoord next;

            LinkedGeoCoord currentCoord = null;
            LinkedGeoCoord nextCoord    = null;

            while (true)
            {
                currentCoord = currentCoord == null ? loop.first : currentCoord.next;
                if (currentCoord == null)
                {
                    break;
                }

                coord     = currentCoord.vertex;
                nextCoord = currentCoord.next == null ? loop.first : currentCoord.next;
                next      = nextCoord.vertex;


                lat = coord.lat;
                lon = coord.lon;
                if (lat < bbox.south)
                {
                    bbox.south = lat;
                }
                if (lon < bbox.west)
                {
                    bbox.west = lon;
                }
                if (lat > bbox.north)
                {
                    bbox.north = lat;
                }
                if (lon > bbox.east)
                {
                    bbox.east = lon;
                }
                // Save the min positive and max negative longitude for
                // use in the transmeridian case
                if (lon > 0 && lon < minPosLon)
                {
                    minPosLon = lon;
                }
                if (lon < 0 && lon > maxNegLon)
                {
                    maxNegLon = lon;
                }
                // check for arcs > 180 degrees longitude, flagging as transmeridian
                if (Math.Abs(lon - next.lon) > Constants.M_PI)
                {
                    isTransmeridian = true;
                }
            }
            // Swap east and west if transmeridian
            if (isTransmeridian)
            {
                bbox.east = maxNegLon;
                bbox.west = minPosLon;
            }
        }
Esempio n. 22
0
        /// <summary>
        /// Convert GeoLoop to bounding box for loop
        /// </summary>
        /// <param name="loop"></param>
        /// <returns></returns>
        public static BBox ToBBox(this LinkedGeoLoop loop)
        {
            if (loop.IsEmpty)
            {
                return(new BBox());
            }

            var     box             = new BBox(-decimal.MaxValue, decimal.MaxValue, -decimal.MaxValue, decimal.MaxValue);
            decimal minPosLon       = decimal.MaxValue;
            decimal maxNegLon       = -decimal.MaxValue;
            bool    isTransmeridian = false;

            decimal lat;
            decimal lon;

            var nodes = loop.Nodes;

            for (int idx = 0; idx < nodes.Count; idx++)
            {
                var coord = nodes[idx];
                var next  = nodes[(idx + 1) % nodes.Count];

                lat = coord.Latitude;
                lon = coord.Longitude;
                if (lat < box.South)
                {
                    box = box.ReplaceSouth(lat);
                }

                if (lon < box.West)
                {
                    box = box.ReplaceWest(lon);
                }

                if (lat > box.North)
                {
                    box = box.ReplaceNorth(lat);
                }

                if (lon > box.East)
                {
                    box = box.ReplaceEast(lon);
                }
                // Save the min positive and max negative longitude for
                // use in the transmeridian case
                if (lon > 0 && lon < minPosLon)
                {
                    minPosLon = lon;
                }

                if (lon < 0 && lon > maxNegLon)
                {
                    maxNegLon = lon;
                }
                // check for arcs > 180 degrees longitude, flagging as transmeridian
                if (Math.Abs(lon - next.Longitude) > Constants.H3.M_PI)
                {
                    isTransmeridian = true;
                }
            }
            // Swap east and west if transmeridian
            if (isTransmeridian)
            {
                box = box.ReplaceEW(maxNegLon, minPosLon);
            }
            return(box);
        }
Esempio n. 23
0
 /// <summary>
 /// Is loop clockwise?
 /// </summary>
 /// <param name="loop"></param>
 /// <returns></returns>
 public static bool IsClockwise(this LinkedGeoLoop loop)
 {
     return(loop.IsClockwiseNormalized(false));
 }
Esempio n. 24
0
        /// <summary>
        /// Is point inside GeoLoop?
        /// </summary>
        /// <param name="loop"></param>
        /// <param name="box"></param>
        /// <param name="coord"></param>
        /// <returns></returns>
        public static bool PointInside(this LinkedGeoLoop loop, BBox box, GeoCoord coord)
        {
            // fail fast if we're outside the bounding box
            if (!box.Contains(coord))
            {
                return(false);
            }

            bool isTransmeridian = box.IsTransmeridian;
            var  contains        = false;

            decimal targetLatitude  = coord.Latitude;
            decimal targetLongitude = coord.Longitude.NormalizeLongitude(isTransmeridian);

            var nodes = loop.Nodes;

            for (int idx = 0; idx < nodes.Count; idx++)
            {
                var a = nodes[idx];
                var b = nodes[(idx + 1) % nodes.Count];

                // Ray casting algo requires the second point to always be higher
                // than the first, so swap if needed
                if (a.Latitude > b.Latitude)
                {
                    var tmp = a;
                    a = b;
                    b = tmp;
                }

                // If we're totally above or below the latitude ranges, the test
                // ray cannot intersect the line segment, so let's move on
                if (targetLatitude < a.Latitude || targetLatitude > b.Latitude)
                {
                    continue;
                }

                decimal aLng = a.Longitude.NormalizeLongitude(isTransmeridian);
                decimal bLng = b.Longitude.NormalizeLongitude(isTransmeridian);

                // Rays are cast in the longitudinal direction, in case a point
                // exactly matches, to decide tiebreakers, bias westerly
                if (Math.Abs(aLng - targetLongitude) < Constants.H3.DBL_EPSILON ||
                    Math.Abs(bLng - targetLongitude) < Constants.H3.DBL_EPSILON)
                {
                    targetLongitude -= Constants.H3.DBL_EPSILON;
                }

                // For the latitude of the point, compute the longitude of the
                // point that lies on the line segment defined by a and b
                // This is done by computing the percent above a the lat is,
                // and traversing the same percent in the longitudinal direction
                // of a to b
                decimal ratio   = (targetLatitude - a.Latitude) / (b.Latitude - a.Latitude);
                decimal testLng =
                    (aLng + (bLng - aLng) * ratio).NormalizeLongitude(isTransmeridian);

                // Intersection of the ray
                if (testLng > targetLongitude)
                {
                    contains = !contains;
                }
            }

            return(contains);
        }
Esempio n. 25
0
 /// <summary>
 /// Whether the winding order of a given loop is clockwise. In GeoJSON,
 /// clockwise loops are always inner loops (holes).
 /// </summary>
 /// <param name="loop">The loop to check</param>
 /// <returns>Whether the loop is clockwise</returns>
 /// <!-- Based off 3.1.1 -->
 public static bool isClockwiseLinkedGeoLoop(LinkedGeoLoop loop)
 {
     return(isClockwiseNormalizedLinkedGeoLoop(loop, false));
 }
Esempio n. 26
0
        /// <summary>
        /// Normalize a LinkedGeoPolygon in-place into a structure following GeoJSON
        /// MultiPolygon rules: Each polygon must have exactly one outer loop, which
        /// must be first in the list, followed by any holes. Holes in this algorithm
        /// are identified by winding order (holes are clockwise), which is guaranteed
        /// by the h3SetToVertexGraph algorithm.
        ///
        /// Input to this function is assumed to be a single polygon including all
        /// loops to normalize. It's assumed that a valid arrangement is possible.
        /// </summary>
        /// <param name="root">Root polygon including all loops</param>
        /// <returns>0 on success, or an error code > 0 for invalid input</returns>
        /// <!-- Based off 3.1.1 -->
        public static int normalizeMultiPolygon(ref LinkedGeoPolygon root)
        {
            // We assume that the input is a single polygon with loops;
            // if it has multiple polygons, don't touch it
            if (root.next != null)
            {
                return(NORMALIZATION_ERR_MULTIPLE_POLYGONS);
            }

            // Count loops, exiting early if there's only one
            int loopCount = countLinkedLoops(ref root);

            if (loopCount <= 1)
            {
                return(NORMALIZATION_SUCCESS);
            }

            int resultCode           = NORMALIZATION_SUCCESS;
            LinkedGeoPolygon polygon = null;
            LinkedGeoLoop    next    = new LinkedGeoLoop();
            int innerCount           = 0;
            int outerCount           = 0;

            // Create an array to hold all of the inner loops. Note that
            // this array will never be full, as there will always be fewer
            // inner loops than outer loops.
            List <LinkedGeoLoop> innerLoops = new List <LinkedGeoLoop>(loopCount);

            for (var k = 0; k < loopCount; k++)
            {
                innerLoops.Add(new LinkedGeoLoop());
            }
            // Create an array to hold the bounding boxes for the outer loops
            List <BBox> bboxes = new List <BBox>(loopCount);

            for (var k = 0; k < loopCount; k++)
            {
                bboxes.Add(new BBox());
            }

            // Get the first loop and unlink it from root
            LinkedGeoLoop loop = root.first;

            root = new LinkedGeoPolygon();

            // Iterate over all loops, moving inner loops into an array and
            // assigning outer loops to new polygons
            while (loop != null)
            {
                if (isClockwiseLinkedGeoLoop(loop))
                {
                    innerLoops[innerCount] = loop;
                    innerCount++;
                }
                else
                {
                    polygon = polygon == null ? root : addNewLinkedPolygon(ref polygon);
                    addLinkedLoop(ref polygon, ref loop);
                    var bb = bboxes[outerCount];
                    bboxFromLinkedGeoLoop(ref loop, ref bb);
                    bboxes[outerCount] = bb;
                    outerCount++;
                }

                // get the next loop and unlink it from this one
                next      = loop.next;
                loop.next = null;
                loop      = next;
            }

            // Find polygon for each inner loop and assign the hole to it
            for (int i = 0; i < innerCount; i++)
            {
                var inner1 = innerLoops[i];
                polygon = findPolygonForHole(ref inner1, ref root, ref bboxes, outerCount);
                if (polygon != null)
                {
                    var inner2 = innerLoops[i];
                    addLinkedLoop(ref polygon, ref inner2);
                    innerLoops[i] = inner2;
                }
                else
                {
                    // If we can't find a polygon (possible with invalid input), then
                    // we need to release the memory for the hole, because the loop has
                    // been unlinked from the root and the caller will no longer have
                    // a way to destroy it with destroyLinkedPolygon.
                    var inner2 = innerLoops[i];
                    destroyLinkedGeoLoop(ref inner2);
                    innerLoops[i] = null;
                    resultCode    = NORMALIZATION_ERR_UNASSIGNED_HOLES;
                }
            }
            return(resultCode);
        }
Esempio n. 27
0
        /// <summary>
        /// Take a given LinkedGeoLoop data structure and check if it
        /// contains a given geo coordinate.
        /// </summary>
        /// <param name="loop">The linked loop</param>
        /// <param name="bbox">The bbox for the loop</param>
        /// <param name="coord">The coordinate to check</param>
        /// <returns>Whether the point is contained</returns>
        /// <!-- Based off 3.1.1 -->
        public static bool pointInsideLinkedGeoLoop(ref LinkedGeoLoop loop, ref BBox bbox, ref GeoCoord coord)
        {
            // fail fast if we're outside the bounding box
            if (!BBox.bboxContains(bbox, coord))
            {
                return(false);
            }
            bool isTransmeridian = BBox.bboxIsTransmeridian(bbox);
            bool contains        = false;

            double lat = coord.lat;
            double lng = NORMALIZE_LON(coord.lon, isTransmeridian);

            GeoCoord       a;
            GeoCoord       b;
            LinkedGeoCoord currentCoord = null;
            LinkedGeoCoord nextCoord    = null;


            while (true)
            {
                currentCoord = currentCoord == null ? loop.first : currentCoord.next;
                if (currentCoord == null)
                {
                    break;
                }

                a         = currentCoord.vertex;
                nextCoord = currentCoord.next == null ? loop.first : currentCoord.next;
                b         = nextCoord.vertex;

                // Ray casting algo requires the second point to always be higher
                // than the first, so swap if needed
                if (a.lat > b.lat)
                {
                    GeoCoord tmp = new GeoCoord(a.lat, a.lon);
                    a = new GeoCoord(b.lat, b.lon);
                    b = new GeoCoord(tmp.lat, tmp.lon);
                }

                // If we're totally above or below the latitude ranges, the test
                // ray cannot intersect the line segment, so let's move on
                if (lat < a.lat || lat > b.lat)
                {
                    continue;
                }

                double aLng = NORMALIZE_LON(a.lon, isTransmeridian);
                double bLng = NORMALIZE_LON(b.lon, isTransmeridian);

                // Rays are cast in the longitudinal direction, in case a point
                // exactly matches, to decide tiebreakers, bias westerly
                if (Math.Abs(aLng - lng) < Constants.EPSILON || Math.Abs(bLng - lng) < Constants.EPSILON)
                {
                    lng -= Constants.DBL_EPSILON;
                }

                // For the latitude of the point, compute the longitude of the
                // point that lies on the line segment defined by a and b
                // This is done by computing the percent above a the lat is,
                // and traversing the same percent in the longitudinal direction
                // of a to b
                double ratio   = (lat - a.lat) / (b.lat - a.lat);
                double testLng =
                    NORMALIZE_LON(aLng + (bLng - aLng) * ratio, isTransmeridian);

                // Intersection of the ray
                if (testLng > lng)
                {
                    contains = !contains;
                }
            }

            return(contains);
        }