예제 #1
0
        public static Api.H3Index GeoToH3(Code.GeoCoord gc, int res)
        {
            var h3 = Code.H3Index.geoToH3(ref gc, res);

            return(new H3Index {
                Value = h3.value
            });
        }
예제 #2
0
        public static Api.GeoCoord H3ToGeo(Code.H3Index h3)
        {
            var blank = new Code.GeoCoord();

            Code.H3Index.h3ToGeo(h3, ref blank);
            return(new GeoCoord {
                Latitude = blank.lat, Longitude = blank.lon
            });
        }
예제 #3
0
        /// <summary>
        /// Encodes a coordinate on the sphere to the FaceIJK address of the containing
        /// cell at the specified resolution.
        /// </summary>
        /// <param name="g">The spherical coordinates to encode.</param>
        /// <param name="res">The desired H3 resolution for the encoding.</param>
        /// <param name="h">The FaceIJK address of the containing cell at resolution res.</param>
        /// <!-- Based off 3.1.1 -->
        public static void _geoToFaceIjk(GeoCoord g, int res, ref FaceIJK h)
        {
            // first convert to hex2d
            Vec2d v = new Vec2d();

            _geoToHex2d(g, res, ref h.face, ref v);

            // then convert to ijk+
            CoordIJK._hex2dToCoordIJK(ref v, ref h.coord);
        }
예제 #4
0
        /// <summary>
        /// Gets the center of a bounding box
        /// </summary>
        /// <param name="bbox">Input bounding box</param>
        /// <param name="center">Output center coordinate</param>
        /// <!-- Based off 3.1.1 -->
        public static void bboxCenter(BBox bbox, ref GeoCoord center)
        {
            center.lat = (bbox.north + bbox.south) / 2.0;
            // If the bbox crosses the antimeridian, shift east 360 degrees
            double east = bboxIsTransmeridian(bbox)
                ? bbox.east + Constants.M_2PI
                : bbox.east;

            center.lon = GeoCoord.constrainLng((east + bbox.west) / 2.0);
        }
예제 #5
0
 /// <summary>
 /// Determines the azimuth to p2 from p1 in radians
 /// </summary>
 /// <param name="p1">The first spherical coordinates</param>
 /// <param name="p2">The second spherical coordinates</param>
 /// <returns>The azimuth in radians from p1 to p2</returns>
 /// <!-- Based off 3.1.1 -->
 public static double _geoAzimuthRads(GeoCoord p1, GeoCoord p2)
 {
     return
         (Math.Atan2
          (
              Math.Cos(p2.lat) * Math.Sin(p2.lon - p1.lon),
              Math.Cos(p1.lat) * Math.Sin(p2.lat) -
              Math.Sin(p1.lat) * Math.Cos(p2.lat) * Math.Cos(p2.lon - p1.lon)
          ));
 }
예제 #6
0
 /// <summary>
 /// Whether the given coordinate has a matching vertex in the given geo boundary.
 /// </summary>
 /// <param name="vertex">Coordinate to check</param>
 /// <param name="boundary">Geo boundary to look in</param>
 /// <returns>Whether a match was found</returns>
 /// <!-- Based off 3.1.1 -->
 public static bool _hasMatchingVertex(GeoCoord vertex, GeoBoundary boundary)
 {
     for (int i = 0; i < boundary.numVerts; i++)
     {
         if (GeoCoord.geoAlmostEqualThreshold(vertex, boundary.verts[i], 0.000001))
         {
             return(true);
         }
     }
     return(false);
 }
예제 #7
0
        /// <summary>
        /// Returns the radius of a given hexagon in kilometers
        /// </summary>
        /// <param name="h3Index">Index of the hexagon</param>
        /// <returns>radius of hexagon in kilometers</returns>
        /// <!-- Based off 3.1.1 -->
        static double _hexRadiusKm(H3Index h3Index)
        {
            // There is probably a cheaper way to determine the radius of a
            // hexagon, but this way is conceptually simple
            GeoCoord    h3Center   = new GeoCoord();
            GeoBoundary h3Boundary = new GeoBoundary();

            H3Index.h3ToGeo(h3Index, ref h3Center);
            H3Index.h3ToGeoBoundary(h3Index, ref h3Boundary);
            return(GeoCoord._geoDistKm(h3Center, h3Boundary.verts));
        }
예제 #8
0
 /// <summary>
 /// Whether the bounding box contains a given point
 /// </summary>
 /// <param name="bbox">Bounding box</param>
 /// <param name="point">Point to test</param>
 /// <returns>true is point is contained</returns>
 /// <!-- Based off 3.1.1 -->
 public static bool bboxContains(BBox bbox, GeoCoord point)
 {
     return
         (point.lat >= bbox.south &&
          point.lat <= bbox.north &&
          (
              bboxIsTransmeridian(bbox)
              // transmeridian case
             ? point.lon >= bbox.west || point.lon <= bbox.east
              // standard case
             : point.lon >= bbox.west && point.lon <= bbox.east
          ));
 }
예제 #9
0
        /// <summary>
        /// Internal: Create a vertex graph from a set of hexagons. It is the
        /// responsibility of the caller to call destroyVertexGraph on the populated
        /// graph, otherwise the memory in the graph nodes will not be freed.
        /// </summary>
        ///
        /// <param name="h3Set">Set of hexagons</param>
        /// <param name="numHexes">Number of hexagons in the set</param>
        /// <param name="graph">Output graph</param>
        /// <!-- Based off 3.1.1 -->
        public static void h3SetToVertexGraph(ref List <H3Index> h3Set, int numHexes,
                                              ref VertexGraph graph)
        {
            GeoBoundary vertices   = new GeoBoundary();
            GeoCoord    fromVertex = new GeoCoord();
            GeoCoord    toVertex   = new GeoCoord();

            VertexGraph.VertexNode edge;
            if (numHexes < 1)
            {
                // We still need to init the graph, or calls to destroyVertexGraph will
                // fail
                graph = new VertexGraph(0, 0);
                return;
            }

            int       res        = H3Index.H3_GET_RESOLUTION(h3Set[0]);
            const int minBuckets = 6;
            // TODO: Better way to calculate/guess?
            int numBuckets = numHexes > minBuckets ? numHexes : minBuckets;

            graph = new VertexGraph(numBuckets, res);

            // Iterate through every hexagon
            for (int i = 0; i < numHexes; i++)
            {
                H3Index.h3ToGeoBoundary(h3Set[i], ref vertices);
                // iterate through every edge
                for (int j = 0; j < vertices.numVerts; j++)
                {
                    fromVertex = new GeoCoord(vertices.verts[j].lat, vertices.verts[j].lon);
                    //fromVtx = vertices.verts[j];
                    int idx = (j + 1) % vertices.numVerts;
                    toVertex = new GeoCoord(vertices.verts[idx].lat, vertices.verts[idx].lon);
                    //toVtx = vertices.verts[(j + 1) % vertices.numVerts];
                    // If we've seen this edge already, it will be reversed
                    edge = VertexGraph.findNodeForEdge(ref graph, toVertex, fromVertex);
                    if (edge != null)
                    {
                        // If we've seen it, drop it. No edge is shared by more than 2
                        // hexagons, so we'll never see it again.
                        VertexGraph.removeVertexNode(ref graph, ref edge);
                    }
                    else
                    {
                        // Add a new node for this edge
                        VertexGraph.addVertexNode(ref graph, fromVertex, toVertex);
                    }
                }
            }
        }
예제 #10
0
        /// <summary>
        /// Determines the center point in spherical coordinates of a cell given by 2D
        /// hex coordinates on a particular icosahedral face.
        /// </summary>
        /// <param name="v">The 2D hex coordinates of the cell.</param>
        /// <param name="face">The icosahedral face upon which the 2D hex coordinate system is centered</param>
        /// <param name="res">The H3 resolution of the cell</param>
        /// <param name="substrate">Indicates whether or not this grid is actually a substrate
        /// grid relative to the specified resolution.</param>
        /// <param name="g">The spherical coordinates of the cell center point.</param>
        /// <!-- Based off 3.1.1 -->
        public static void _hex2dToGeo(ref Vec2d v, int face, int res, int substrate, ref GeoCoord g)
        {
            // calculate (r, theta) in hex2d
            double r = Vec2d._v2dMag(v);

            if (r < Constants.EPSILON)
            {
                g = faceCenterGeo[face];
                return;
            }

            double theta = Math.Atan2(v.y, v.x);

            // scale for current resolution length u
            for (int i = 0; i < res; i++)
            {
                r /= M_SQRT7;
            }

            // scale accordingly if this is a substrate grid
            if (substrate > 0)
            {
                r /= 3.0;
                if (H3Index.isResClassIII(res))
                {
                    r /= M_SQRT7;
                }
            }

            r *= Constants.RES0_U_GNOMONIC;

            // perform inverse gnomonic scaling of r
            r = Math.Atan(r);

            // adjust theta for Class III
            // if a substrate grid, then it's already been adjusted for Class III
            if (substrate == 0 && H3Index.isResClassIII(res))
            {
                theta = GeoCoord._posAngleRads(theta + Constants.M_AP7_ROT_RADS);
            }

            // find theta as an azimuth
            theta = GeoCoord._posAngleRads(faceAxesAzRadsCII[face, 0] - theta);

            // now find the point at (r,theta) from the face center
            GeoCoord._geoAzDistanceRads(ref faceCenterGeo[face], theta, r, ref g);
        }
예제 #11
0
        /// <summary>
        /// Find the <see cref="VertexNode"/> for a given edge, if it exists.
        /// </summary>
        /// <param name="graph">Graph to look in</param>
        /// <param name="fromVtx">Start Vertex</param>
        /// <param name="ToVtx">End Vertex, or null if we don't care</param>
        /// <returns>Pointer to the vertex node, if found</returns>
        /// <!-- Based off 3.1.1 -->
        public static VertexNode findNodeForEdge(
            ref VertexGraph graph,
            GeoCoord fromVtx,
            GeoCoord toVtx)
        {
            uint index         = _hashVertex(fromVtx, graph.res, graph.numBuckets);
            var  currentBucket = graph.buckets[(int)index];

            var nodeIndex = currentBucket.FindIndex(
                t => GeoCoord.geoAlmostEqual(t.from, fromVtx) &&
                (toVtx == null || GeoCoord.geoAlmostEqual(t.to, toVtx))
                );

            return(nodeIndex < 0
                ? null
                : currentBucket[nodeIndex]);
        }
예제 #12
0
        /// <summary>
        /// Encodes a coordinate on the sphere to the H3 index of the containing cell at
        /// the specified resolution.
        ///
        /// Returns 0 on invalid input.
        /// </summary>
        /// <param name="g">The spherical coordinates to encode.</param>
        /// <param name="res"> The desired H3 resolution for the encoding.</param>
        /// <returns>The encoded H3Index (or 0 on failure).</returns>
        /// <!-- Based off 3.1.1 -->
        public static H3Index geoToH3(ref GeoCoord g, int res)
        {
            if (res < 0 || res > Constants.MAX_H3_RES)
            {
                return(H3_INVALID_INDEX);
            }

            if (Double.IsInfinity(g.lat) || Double.IsNaN(g.lat) ||
                Double.IsInfinity(g.lon) || Double.IsNaN(g.lon))
            {
                return(H3_INVALID_INDEX);
            }

            FaceIJK fijk = new FaceIJK();

            FaceIJK._geoToFaceIjk(g, res, ref fijk);
            return(_faceIjkToH3(ref fijk, res));
        }
예제 #13
0
        /// <summary>
        /// Get an integer hash for a lat/lon point, at a precision determined
        /// by the current hexagon resolution.
        /// </summary>
        /// <remarks>
        /// Light testing suggests this might not be sufficient at resolutions
        /// finer than 10. Design a better hash function if performance and
        /// collisions seem to be an issue here. (Modified in code below)
        /// </remarks>
        /// <param name="vertex">Lat/lon vertex to hash</param>
        /// <param name="res">Resolution of the hexagon the vertex belongs to</param>
        /// <param name="numBuckets">Number of buckets in the graph</param>
        /// <returns>Integer hash</returns>
        /// <!-- Based off 3.1.1 -->
        public static uint _hashVertex(GeoCoord vertex, int res, int numBuckets)
        {
            // Simple hash: Take the sum of the lat and lon with a precision level
            // determined by the resolution, converted to int, modulo bucket count.
            //  return (uint)
            //    Math.IEEERemainder
            //        (
            //         Math.Abs((vertex.lat + vertex.lon) * Math.Pow(10, 15 - res)),
            //         numBuckets
            //        );
            //
            // I didn't like that one because it caused TestVertexGraph to
            // fail, so I wrote a new one.

            //  Edge cases for stuff close enough to (0,0) to not matter go straight to bucket 0.
            if (vertex == null)
            {
                return(0);
            }
            double start_lat = Math.Abs(vertex.lat);
            double start_lon = Math.Abs(vertex.lon);

            if (start_lon < Constants.DBL_EPSILON && start_lat < Constants.DBL_EPSILON)
            {
                return(0);
            }

            const int keepShifting = 1000000000;
            double    start        = 0;

            while (start < keepShifting)
            {
                start_lat *= 9973;
                start_lon *= 911;
                start     += start_lat;
                start     += start_lon;
            }

            var fraction = Math.IEEERemainder(start, 1);

            return((uint)(Math.Abs(fraction) * numBuckets));
        }
예제 #14
0
        /// <summary>
        /// Provides the coordinates defining the unidirectional edge.
        /// </summary>
        /// <param name="edge">The unidirectional edge H3Index</param>
        /// <param name="gb">
        /// The geoboundary object to store the edge coordinates.
        /// </param>
        /// <!-- Based off 3.1.1 -->
        public static void getH3UnidirectionalEdgeBoundary(H3Index edge, ref GeoBoundary gb)
        {
            // TODO: More efficient solution :)
            GeoBoundary origin             = new GeoBoundary();
            GeoBoundary destination        = new GeoBoundary();
            GeoCoord    postponedVertex    = new GeoCoord();
            bool        hasPostponedVertex = false;

            H3Index.h3ToGeoBoundary(getOriginH3IndexFromUnidirectionalEdge(edge), ref origin);
            H3Index.h3ToGeoBoundary(getDestinationH3IndexFromUnidirectionalEdge(edge), ref destination);

            int k = 0;

            for (int i = 0; i < origin.numVerts; i++)
            {
                if (_hasMatchingVertex(origin.verts[i], destination))
                {
                    // If we are on vertex 0, we need to handle the case where it's the
                    // end of the edge, not the beginning.
                    if (i == 0 &&
                        !_hasMatchingVertex(origin.verts[i + 1], destination))
                    {
                        postponedVertex    = origin.verts[i];
                        hasPostponedVertex = true;
                    }
                    else
                    {
                        gb.verts[k] = origin.verts[i];
                        k++;
                    }
                }
            }

            // If we postponed adding the last vertex, add it now
            if (hasPostponedVertex)
            {
                gb.verts[k] = postponedVertex;
                k++;
            }
            gb.numVerts = k;
        }
예제 #15
0
        /// <summary>
        /// Find the great circle distance in radians between two spherical coordinates.
        /// </summary>
        /// <param name="p1">The first spherical coordinates</param>
        /// <param name="p2">The second spherical coordinates</param>
        /// <returns>The great circle distance between p1 and p2</returns>
        /// <!-- Based off 3.1.1 -->
        public static double _geoDistRads(GeoCoord p1, GeoCoord p2)
        {
            // use spherical triangle with p1 at A, p2 at B, and north pole at C
            double bigC = Math.Abs(p2.lon - p1.lon);

            if (bigC > Constants.M_PI) // assume we want the complement
            {
                // note that in this case they can't both be negative
                double lon1 = p1.lon;
                if (lon1 < 0.0)
                {
                    lon1 += 2.0 * Constants.M_PI;
                }
                double lon2 = p2.lon;
                if (lon2 < 0.0)
                {
                    lon2 += 2.0 * Constants.M_PI;
                }

                bigC = Math.Abs(lon2 - lon1);
            }

            double b = Constants.M_PI_2 - p1.lat;
            double a = Constants.M_PI_2 - p2.lat;

            // use law of cosines to find c
            double cosc = Math.Cos(a) * Math.Cos(b) + Math.Sin(a) * Math.Sin(b) * Math.Cos(bigC);

            if (cosc > 1.0)
            {
                cosc = 1.0;
            }

            if (cosc < -1.0)
            {
                cosc = -1.0;
            }

            return(Math.Acos(cosc));
        }
예제 #16
0
        /// <summary>Add an edge to the graph</summary>
        /// <param name="graph">Graph to add node to</param>
        /// <param name="fromVtx">Start vertex</param>
        /// <param name="toVtx">End vertex</param>
        /// <returns>new node</returns>
        /// <!-- Based off 3.1.1 -->
        public static VertexNode addVertexNode(ref VertexGraph graph, GeoCoord fromVtx,
                                               GeoCoord toVtx)
        {
            // Make the new node
            VertexNode node = _initVertexNode(fromVtx, toVtx);
            // Determine location
            var index = _hashVertex(fromVtx, graph.res, graph.numBuckets);
            // Check whether there's an existing node in that spot
            List <VertexNode> currentNode = graph.buckets[(int)index];

            if (currentNode.Count == 0)
            {
                // Set bucket to the new node
                graph.buckets[(int)index].Add(node);
            }
            else
            {
                //  Go through the list to make sure the
                //  edge doesn't already exist
                //
                //  NOTE: Later, use a Hashset
                foreach (var vertexNode in graph.buckets[(int)index])
                {
                    if (GeoCoord.geoAlmostEqual(vertexNode.from, fromVtx) &&
                        GeoCoord.geoAlmostEqual(vertexNode.to, toVtx))
                    {
                        //  already exists, bail.
                        return(vertexNode);
                    }
                }
                // Add the new node to the end of the list
                graph.buckets[(int)index].Add(node);
            }
            graph.size++;
            return(node);
        }
예제 #17
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);
        }
예제 #18
0
 /// <summary>
 /// Determines if the components of two spherical coordinates are within our
 /// standard epsilon distance of each other.
 /// </summary>
 /// <param name="p1">The first spherical coordinates.</param>
 /// <param name="p2">The second spherical coordinates.</param>
 /// <returns>
 ///  Whether or not the two coordinates are within the epsilon distance
 /// of each other.
 /// </returns>
 /// <!-- Based off 3.1.1 -->
 public static bool geoAlmostEqual(GeoCoord v1, GeoCoord v2)
 {
     return(geoAlmostEqualThreshold(v1, v2, Constants.EPSILON_RAD));
 }
예제 #19
0
 /// <summary>
 /// Determines if the components of two spherical coordinates are within some
 /// threshold distance of each other.
 /// </summary>
 /// <param name="p1">The first spherical coordinates</param>
 /// <param name="p2">The second spherical coordinates</param>
 /// <param name="threshold">The threshold distance</param>
 /// <returns>
 /// Whether or not the two coordinates are within the threshold distance
 /// of each other
 /// </returns>
 /// <!-- Based off 3.1.1 -->
 public static bool geoAlmostEqualThreshold(GeoCoord p1, GeoCoord p2, double threshold)
 {
     return(Math.Abs(p1.lat - p2.lat) < threshold &&
            Math.Abs(p1.lon - p2.lon) < threshold);
 }
예제 #20
0
        /// <summary>
        /// Computes the point on the sphere a specified azimuth and distance from
        /// another point.
        /// </summary>
        /// <param name="p1">The first spherical coordinates.</param>
        /// <param name="az">The desired azimuth from p1.</param>
        /// <param name="distance">The desired distance from p1, must be non-negative.</param>
        /// <param name="p2">
        /// The spherical coordinates at the desired azimuth and distance from p1.
        /// </param>
        /// <!-- Based off 3.1.1 -->
        public static void _geoAzDistanceRads(ref GeoCoord p1, double az, double distance, ref GeoCoord p2)
        {
            if (distance < Constants.EPSILON)
            {
                p2 = p1;
                return;
            }

            double sinlat, sinlon, coslon;

            az = _posAngleRads(az);

            // check for due north/south azimuth
            if (az < Constants.EPSILON || Math.Abs(az - Constants.M_PI) < Constants.EPSILON)
            {
                if (az < Constants.EPSILON)  // due north
                {
                    p2.lat = p1.lat + distance;
                }
                else  // due south
                {
                    p2.lat = p1.lat - distance;
                }

                if (Math.Abs(p2.lat - Constants.M_PI_2) < Constants.EPSILON)  // north pole
                {
                    p2.lat = Constants.M_PI_2;
                    p2.lon = 0.0;
                }
                else if (Math.Abs(p2.lat + Constants.M_PI_2) < Constants.EPSILON)    // south pole
                {
                    p2.lat = -Constants.M_PI_2;
                    p2.lon = 0.0;
                }
                else
                {
                    p2.lon = constrainLng(p1.lon);
                }
            }
            else    // not due north or south
            {
                sinlat = Math.Sin(p1.lat) * Math.Cos(distance) +
                         Math.Cos(p1.lat) * Math.Sin(distance) * Math.Cos(az);
                if (sinlat > 1.0)
                {
                    sinlat = 1.0;
                }
                if (sinlat < -1.0)
                {
                    sinlat = -1.0;
                }
                p2.lat = Math.Asin(sinlat);
                if (Math.Abs(p2.lat - Constants.M_PI_2) < Constants.EPSILON)  // north pole
                {
                    p2.lat = Constants.M_PI_2;
                    p2.lon = 0.0;
                }
                else if (Math.Abs(p2.lat + Constants.M_PI_2) < Constants.EPSILON)    // south pole
                {
                    p2.lat = -Constants.M_PI_2;
                    p2.lon = 0.0;
                }
                else
                {
                    sinlon = Math.Sin(az) * Math.Sin(distance) / Math.Cos(p2.lat);
                    coslon = (Math.Cos(distance) - Math.Sin(p1.lat) * Math.Sin(p2.lat)) /
                             Math.Cos(p1.lat) / Math.Cos(p2.lat);
                    if (sinlon > 1.0)
                    {
                        sinlon = 1.0;
                    }
                    if (sinlon < -1.0)
                    {
                        sinlon = -1.0;
                    }
                    if (coslon > 1.0)
                    {
                        sinlon = 1.0;
                    }
                    if (coslon < -1.0)
                    {
                        sinlon = -1.0;
                    }
                    p2.lon = constrainLng(p1.lon + Math.Atan2(sinlon, coslon));
                }
            }
        }
예제 #21
0
 /// <summary>
 /// Find a Vertex node starting at the given vertex
 /// </summary>
 /// <param name="graph">Graph to look in</param>
 /// <param name="fromVtx">Start vertex</param>
 /// <returns>Vertex node, if found</returns>
 /// <!-- Based off 3.1.1 -->
 public static VertexNode findNodeForVertex(
     ref VertexGraph graph,
     ref GeoCoord fromVtx)
 {
     return(findNodeForEdge(ref graph, fromVtx, null));
 }
예제 #22
0
 /// <summary>
 /// Find the great circle distance in kilometers between two spherical
 /// coordinates
 /// </summary>
 /// <param name="p1">The first spherical coordinates</param>
 /// <param name="p2">The distance in kilometers between p1 and p2</param>
 /// <!-- Based off 3.1.1 -->
 public static double _geoDistKm(GeoCoord p1, IEnumerable <GeoCoord> p2)
 {
     return(Constants.EARTH_RADIUS_KM * _geoDistRads(p1, p2.First( )));
 }
예제 #23
0
 /// <summary>
 /// Set the components of spherical coordinates in radians.
 /// </summary>
 /// <param name="p">The spherical coordinates</param>
 /// <param name="latDegs">The desired latitude in decimal radians</param>
 /// <param name="lonDegs">The desired longitude in decimal radians</param>
 /// <!-- Based off 3.1.1 -->
 public static void _setGeoRads(ref GeoCoord p, double latRads, double lonRads)
 {
     p.lat = latRads;
     p.lon = lonRads;
 }
예제 #24
0
 /// <summary>
 /// Set the components of spherical coordinates in decimal degrees.
 /// </summary>
 /// <param name="p">The spherical coordinates</param>
 /// <param name="latDegs">The desired latitude in decimal degrees</param>
 /// <param name="lonDegs">The desired longitude in decimal degrees</param>
 /// <!-- Based off 3.1.1 -->
 public static void setGeoDegs(ref GeoCoord p, double latDegs, double lonDegs)
 {
     _setGeoRads(ref p, degsToRads(latDegs), degsToRads(lonDegs));
 }
예제 #25
0
        /// <summary>
        /// pointInside is the core loop of the point-in-poly algorithm
        /// </summary>
        /// <param name="loop">The loop to check</param>
        /// <param name="bbox">The bbox for the loop being tested</param>
        /// <param name="coord">The coordinate to check</param>
        /// <returns>Whether the point is contained</returns>
        /// <!-- Based off 3.1.1 -->
        public static bool pointInsideGeofence(ref Geofence 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;

            int loopIndex = -1;

            while (true)
            {
                if (++loopIndex >= loop.numVerts)
                {
                    break;
                }
                a = new GeoCoord(loop.verts[loopIndex].lat, loop.verts[loopIndex].lon);
                b = new GeoCoord
                    (
                    loop.verts[(loopIndex + 1) % loop.numVerts].lat,
                    loop.verts[(loopIndex + 1) % loop.numVerts].lon
                    );


                //b = loop.verts[(loopIndex + 1) % loop.numVerts];

                // 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 = 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 (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.DBL_EPSILON || Math.Abs(bLng - lng) < Constants.DBL_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);
        }
예제 #26
0
        /// <summary>
        /// takes a given GeoPolygon data structure and
        /// checks if it contains a given geo coordinate.
        /// </summary>
        /// <param name="geoPolygon">The Geofence and holes defining the relevant area</param>
        /// <param name="bboxes">The bboxes for the main Geofence and each of its holes</param>
        /// <param name="coord">The coordinate to check</param>
        /// <returns>Whether the point is contained</returns>
        /// <!-- Based off 3.1.1 -->
        public static bool pointInsidePolygon(GeoPolygon geoPolygon, List <BBox> bboxes, GeoCoord coord)
        {
            // Start with contains state of primary Geofence
            var  tempBox  = bboxes[0];
            bool contains = pointInsideGeofence(
                ref geoPolygon.Geofence,
                ref tempBox, ref coord);

            bboxes[0] = tempBox;

            // If the point is contained in the primary Geofence, but there are holes in
            // the Geofence iterate through all holes and return false if the point is
            // contained in any hole
            if (contains && geoPolygon.numHoles > 0)
            {
                for (int i = 0; i < geoPolygon.numHoles; i++)
                {
                    var hole     = geoPolygon.holes[i];
                    var box      = bboxes[i + 1];
                    var isInside = pointInsideGeofence(ref hole, ref box, ref coord);
                    geoPolygon.holes[i] = hole;
                    bboxes[i + 1]       = box;

                    if (isInside)
                    {
                        return(false);
                    }
                }
            }
            return(contains);
        }
예제 #27
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">Output bbox</param>
        /// <!-- Based off 3.1.1 -->
        public static void bboxFromGeofence(ref Geofence loop, ref BBox bbox)
        {
            // Early exit if there are no vertices
            if (loop.numVerts == 0)
            {
                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;

            int loopIndex = -1;

            while (true)
            {
                if (++loopIndex >= loop.numVerts)
                {
                    break;
                }

                coord = new GeoCoord(loop.verts[loopIndex].lat, loop.verts[loopIndex].lon);
                next  = new GeoCoord
                        (
                    loop.verts[(loopIndex + 1) % loop.numVerts].lat,
                    loop.verts[(loopIndex + 1) % loop.numVerts].lon
                        );



                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;
            }
        }
예제 #28
0
 public GeoCoord(Code.GeoCoord gc)
 {
     Latitude  = gc.lat;
     Longitude = gc.lon;
 }
예제 #29
0
        ///<summary>
        /// polyfill takes a given GeoJSON-like data structure and preallocated,
        /// zeroed memory, and fills it with the hexagons that are contained by
        /// the GeoJSON-like data structure.
        ///
        ///  The current implementation is very primitive and slow, but correct,
        /// performing a point-in-poly operation on every hexagon in a k-ring defined
        /// around the given Geofence.
        /// </summary>
        /// <param name="geoPolygon">The Geofence and holes defining the relevant area</param>
        /// <param name="res"> The Hexagon resolution (0-15)</param>
        /// <param name="out_hex">The slab of zeroed memory to write to. Assumed to be big enough.</param>
        /// <!-- Based off 3.1.1 -->
        internal static void polyfill(GeoPolygon geoPolygon, int res, List <H3Index> out_hex)
        {
            // One of the goals of the polyfill algorithm is that two adjacent polygons
            // with zero overlap have zero overlapping hexagons. That the hexagons are
            // uniquely assigned. There are a few approaches to take here, such as
            // deciding based on which polygon has the greatest overlapping area of the
            // hexagon, or the most number of contained points on the hexagon (using the
            // center point as a tiebreaker).
            //
            // But if the polygons are convex, both of these more complex algorithms can
            // be reduced down to checking whether or not the center of the hexagon is
            // contained in the polygon, and so this is the approach that this polyfill
            // algorithm will follow, as it's simpler, faster, and the error for concave
            // polygons is still minimal (only affecting concave shapes on the order of
            // magnitude of the hexagon size or smaller, not impacting larger concave
            // shapes)
            //
            // This first part is identical to the maxPolyfillSize above.

            // Get the bounding boxes for the polygon and any holes
            int         cnt    = geoPolygon.numHoles + 1;
            List <BBox> bboxes = new List <BBox>();

            for (int i = 0; i < cnt; i++)
            {
                bboxes.Add(new BBox());
            }

            Polygon.bboxesFromGeoPolygon(geoPolygon, ref bboxes);

            int minK        = BBox.bboxHexRadius(bboxes[0], res);
            int numHexagons = maxKringSize(minK);

            // Get the center hex
            GeoCoord center = new GeoCoord();

            BBox.bboxCenter(bboxes[0], ref center);
            H3Index centerH3 = H3Index.geoToH3(ref center, res);

            // From here on it works differently, first we get all potential
            // hexagons inserted into the available memory
            kRing(centerH3, minK, ref out_hex);

            // Next we iterate through each hexagon, and test its center point to see if
            // it's contained in the GeoJSON-like struct
            for (int i = 0; i < numHexagons; i++)
            {
                // Skip records that are already zeroed
                if (out_hex[i] == 0)
                {
                    continue;
                }
                // Check if hexagon is inside of polygon
                GeoCoord hexCenter = new GeoCoord();
                H3Index.h3ToGeo(out_hex[i], ref hexCenter);
                hexCenter.lat = GeoCoord.constrainLat(hexCenter.lat);
                hexCenter.lon = GeoCoord.constrainLng(hexCenter.lon);
                // And remove from list if not
                if (!Polygon.pointInsidePolygon(geoPolygon, bboxes, hexCenter))
                {
                    out_hex[i] = H3Index.H3_INVALID_INDEX;
                }
            }
        }