Ejemplo n.º 1
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);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Create a bounding box from a simple polygon defined as an array of vertices.
        ///
        /// Known limitations:
        /// - Does not support polygons with two adjacent points &gt; 180 degrees of
        ///   longitude apart. These will be interpreted as crossing the antimeridian.
        /// - Does not currently support polygons containing a pole.
        /// </summary>
        /// <param name="verts">Array of vertices</param>
        /// <param name="numVerts">Number of vertices</param>
        /// <param name="bbox">Output box</param>
        /// <!-- Based off 3.1.1 -->
        static void bboxFromVertices(List <GeoCoord> verts, int numVerts, ref BBox bbox)
        {
            // Early exit if there are no vertices
            if (numVerts == 0)
            {
                bbox.north = 0;
                bbox.south = 0;
                bbox.east  = 0;
                bbox.west  = 0;
                return;
            }
            double lat;
            double lon;

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

            for (int i = 0; i < numVerts; i++)
            {
                lat = verts[i].lat;
                lon = verts[i].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;
                }
                // check for arcs > 180 degrees longitude, flagging as transmeridian
                if (Math.Abs(lon - verts[(i + 1) % numVerts].lon) > Constants.M_PI)
                {
                    isTransmeridian = true;
                }
            }
            // Swap east and west if transmeridian
            if (isTransmeridian)
            {
                double tmp = bbox.east;
                bbox.east = bbox.west;
                bbox.west = tmp;
            }
        }
Ejemplo n.º 3
0
        /// <summary>
        /// maxPolyfillSize returns the number of hexagons to allocate space for when
        /// performing a polyfill on the given GeoJSON-like data structure.
        ///
        /// Currently a laughably padded response, being a k-ring that wholly contains
        /// a bounding box of the GeoJSON, but still less wasted memory than initializing
        /// a Python application? ;)
        /// </summary>
        /// <param name="geoPolygon">A GeoJSON-like data structure indicating the poly to fill</param>
        /// <param name="res">Hexagon resolution (0-15)</param>
        /// <returns>number of hexagons to allocate for</returns>
        /// <!-- Based off 3.1.1 -->
        internal static int maxPolyfillSize(ref GeoPolygon geoPolygon, int res)
        {
            // Get the bounding box for the GeoJSON-like struct
            BBox bbox = new BBox();

            Polygon.bboxFromGeofence(ref geoPolygon.Geofence, ref bbox);
            int minK = BBox.bboxHexRadius(bbox, res);

            // The total number of hexagons to allocate can now be determined by
            // the k-ring hex allocation helper function.
            return(maxKringSize(minK));
        }
Ejemplo n.º 4
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
          ));
 }
Ejemplo n.º 5
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;
                }
            }
        }
Ejemplo n.º 6
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;
            }
        }
Ejemplo n.º 7
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);
        }
Ejemplo n.º 8
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;
            }
        }
Ejemplo n.º 9
0
 /// <summary>
 /// Whether the given bounding box cross the antimeridian
 /// </summary>
 /// <param name="bbox">bounding box to inspect</param>
 /// <returns>true if transmeridian</returns>
 /// <!-- Based off 3.1.1 -->
 public static bool bboxIsTransmeridian(BBox bbox)
 {
     return(bbox.east < bbox.west);
 }
Ejemplo n.º 10
0
 /// <summary>
 /// Create a bounding box from a Geofence
 /// </summary>
 /// <param name="Geofence">Input <see cref="Geofence"/></param>
 /// <param name="bbox">Output bbox</param>
 /// <!-- Based off 3.1.1 -->
 public static void bboxFromGeofence(Geofence Geofence, ref BBox bbox)
 {
     bboxFromVertices(Geofence.verts.ToList(), Geofence.numVerts, ref bbox);
 }