Covering an arbitary area with Circles of equal radius
iOS geo mercator hexgrid longRead Estimated reading time: 15 minutesMaps help us to visualize different aspects of our work. We use them when we want to get information about the place when we order a taxi and in many other cases.
Recently, I received a task to calculate a centers position of the areas with the equal area that is evenly distributed inside picked by user area (inside some polygon).
We can simplify the task by saying that we should “eventually fill (or cover) the polygon with smaller shapes of equal size (circles, for example)” on map.
The problem
Such an approach can help a lot for a different kind of task, and I believe there are a lot of existing algorithms that may help us to do this. Below, I tried to represent my approach to doing this.
k-center clustering one of algorithm. On SO there is also a few related to this algorithm questions exist, like this one
If u need to evenly distribute something over a certain area, or create a kind of protective shield on some area - this algorithm - is exactly what are u looking for.
To cover the space with regular polygon, only 3 tessellations of 2D space exists - use squares, triangles, or hexagons. In case if we would like to use other shapes, like a circle, overlapping will be present.
If u review all 3 approaches, then, basically, u can see that all of them consist of triangles - 1 or more
If we talking about circles as a covering shape, then hexagons are the ideal solution - it represents a circle that a bit triangulated.
The idea - is to tessellate hexagons and then circumscribe a circle to every polygon. Hexagon as a basic shape reduces the amount of overlapping.
This method is known as Hexagonal tiling. If we talk about volume - Close-packing of equal spheres may be helpful
The Idea
I was thinking about a possible way of calculating centers of covering shapes and how to cover the shape with hexagons. Then, found pretty similar description of the idea by baskax
as I was thinking about:
1) Define the polygon area to be covered
2) Define the parameters of the shape to be used as covering shape
3) Cover the full area with a hexagon (or triangles or squares)
4) Determine what centers of shapes are inside of your initial polygon and select them
5) Replace shape from p3. to selected shape
As always, one picture is better that 1000 words:
The Solution
Here began the most interesting part. As usual, the idea is the main thing, but the realization is the most interesting.
According to our idea, there is must be a few steps, that should be done before we actually can get the result.
Define the polygon area to be covered
At first look, this is a simple task - the user can just tap on the map and select the area, so we just create a polygon from the input points.
But, if user pick points not one-by-one, or pick some points inside already selected area… As result, the polygon can be a bit unexpected:
We can play a bit, and disable the points that are added by the user and already inside a polygon - the result will be a bit better, but, the order of newly added points can be still confusing, so additional kinds of sorting and arranging points may require.
There are a lot of algorithms that can help us to solve this problem. I decided to go with convex hull.
“The convex hull is a polygon with the shortest perimeter that encloses a set of points. As a visual analogy, consider a set of points as nails in aboard. The convex hull of the points would be like a rubber band stretched around the outermost nails.”
source of the image
My solution for this based on open-source solution from Rosseta:
and to transformed modification to use with location:
The result of this can be shown on map as:
Various approaches have a different downside. For the selected algorithm this will be the limited possibilities of the selected zone. U actually can’t create a polygon with an acute angle. Selecting a new algorithm may be a good point for improvements.
Define the parameters of the shape to be used as covering shape
If we look back, we can see, that this step is already determined.
To summarize, here is the list of the required data for us:
1) A set of points to be processed for created proper polygon 2) Area definition and unit system definition for polygons that will be used for covering picked by user location
Cover the full area with hexagon
This is a very interesting step.
As u read earlier, to follow our idea, we should define a bounding box for picked polygon, and then, starting from one corner fill this bounding box with hexagons, saving the center of each hexagon as a possible center for our covering shapes.
Bounding box
To determine the bounding box, we should think about Earth, and how it is represented on a map. The cylindrical projection is used in most maps, another name for it - Mercator map.
For us, this is good, because as u can see - it’s just a 2D coordinate system, so we should find only 2 points to make the job done.
We can use an existing API from
MapKit
and foundMKCoordinateRegion
, and then convert it to the bounding box, as it’s described here or here, but, I think, then pure solution a bit better - it can provide a clear vision of how it works for us, so understanding of the process will be much better.
I used GeoTools from wpearse
as a base point, and have prepared a swift version of boundingBox.
Instead of naming like topLeft
, bottomRight
etc corners, for map better to use combination of East
West
South
and North
. To better understand this, here is the compas:
The code for finding bounding box should simply define min values for each corners and iterate througth all points by comparing lat/long to initial value. In case of difference store the new value if needed as minimum:
the full source code available in the download section at the end of the article
Always check different lat/long for any work related to the map.
Making hexagon mask for area
Now, when we have a bounding box, we should cover it with hexagons.
Everything u need to know about hexagon grids can be found at this ultimate guide.
The idea is to get the southWest
corner, then move to the east and north.
The naive approach (my first attempt) can be next - we can think about bounding box as a regular rect (thus our map Mercator is 2D).
Other approaches of how to calculate grid can be found here
As u can see, we should somehow convert meters into degrees (toDegree
computed prop.) and add diff in latitude to the coordinate, to find the next center of the hexagon.
To better understand how latitude and logitude works, here is the small reference:
My first thing was - “I can use average value”, so, I decided to use average Earth radius for all latitudes
The result was, that on different locations (depends from latitude) the offset between hexagons are different. If I would like to have 50m between centers, I can get from 32m to 121m:
Thus I used the average radius of the Earth, I tried to remove this inconsistency. To do so, I read about ways how to calculate Earth’s radius.
The first try was to use some koef for different latitude:
This gave a bit better result, so I decided to use a more precise method.
The formula for Earth radius calculation:
I translated it into the code:
But, the result was still not good - deviation was still present.
I read, that normally during navigatio a bearing is used - the horizontal angle between the direction of an object and another object source.
Reading this, I realize, that problem was a bit more complex - I should not just correct the distance by using a more precise calculation of Earth radius, but also use bearing during the next point calculation.
Here is a bearing reference:
To represent bearing, I added simple struct:
Now, we can use current point and bearing to properly calculate the next point:
The result was amazing:
The final code:
Determine what centers of shapes are inside of your initial polygon and select them
Now, the last part - is to determine what exactly hexagons are needed.
To do so, I created a simple protocol, that may represent the required logic:
Thus, I used polygons and Maps, I know, that MapKit
polygon can provide such functionality, so one of the filters may be based on MKPolygon
logic.
This is not ideal, I’m planning to replace convex hull algorithm to more precise and, based on new algorithm use new
PolygonFilter
The code for MKPolygonFilter
:
And the usage:
The result:
Demo
The full demo:
Resource
Convex hull
- Convex hull types
- Convex hull set 2 Graham scan
- Rosseta
- Closed convex hull CLLocationCoordinate2D
- Convex Hull Swift
- ConcaveHull
- Hull.js - JavaScript library that builds concave hull by set of points
- Javascript Convex Hull
- SO Sort latitude and longitude coordinates into clockwise ordered quadrilateral
Bounding box
- Mercator map
- Mercator projection
- SO - How to create a bounding box for geolocation
- SO - How to calculate geography bounding box in iOS?
- GeoTools
Hexagon grid
- The ultimate guide to hexagons
- SO Faster way to calculate hexagon grid coordinates
- Meter to degree equivalent
- SO What are the lengths of Location Coordinates, latitude, and longitude? [closed]
- GIS Calculating the earth radius at latitude
- SO How to convert latitude or longitude to meters?
- Earch radius
- Bearing
- SO Get lat/long given current point, distance and bearing
- GIS Algorithm for offsetting a latitude/longitude by some amount of meters
- GIS How to know what value use to convert meter in degree using Google Maps info [duplicate]
Map tools online
Share on: