Faster way to calculate hexagon grid coordinates

2024/9/29 13:26:04

I'm using the following procedure to calculate hexagonal polygon coordinates of a given radius for a square grid of a given extent (lower left --> upper right):

def calc_polygons(startx, starty, endx, endy, radius):sl = (2 * radius) * math.tan(math.pi / 6)# calculate coordinates of the hexagon pointsp = sl * 0.5b = sl * math.cos(math.radians(30))w = b * 2h = 2 * slorigx = startxorigy = starty# offsets for moving along and up rowsxoffset = byoffset = 3 * ppolygons = []row = 1counter = 0while starty < endy:if row % 2 == 0:startx = origx + xoffsetelse:startx = origxwhile startx < endx:p1x = startxp1y = starty + pp2x = startxp2y = starty + (3 * p)p3x = startx + bp3y = starty + hp4x = startx + wp4y = starty + (3 * p)p5x = startx + wp5y = starty + pp6x = startx + bp6y = startypoly = [(p1x, p1y),(p2x, p2y),(p3x, p3y),(p4x, p4y),(p5x, p5y),(p6x, p6y),(p1x, p1y)]polygons.append(poly)counter += 1startx += wstarty += yoffsetrow += 1return polygons

This works well for polygons into the millions, but quickly slows down (and takes up very large amounts of memory) for large grids. I'm wondering whether there's a way to optimise this, possibly by zipping together numpy arrays of vertices that have been calculated based on the extents, and removing the loops altogether – my geometry isn't good enough for this, however, so any suggestions for improvements are welcome.

Answer

Decompose the problem into the regular square grids (not-contiguous). One list will contain all shifted hexes (i.e. the even rows) and the other will contain unshifted (straight) rows.

def calc_polygons_new(startx, starty, endx, endy, radius):sl = (2 * radius) * math.tan(math.pi / 6)# calculate coordinates of the hexagon pointsp = sl * 0.5b = sl * math.cos(math.radians(30))w = b * 2h = 2 * sl# offsets for moving along and up rowsxoffset = byoffset = 3 * prow = 1shifted_xs = []straight_xs = []shifted_ys = []straight_ys = []while startx < endx:xs = [startx, startx, startx + b, startx + w, startx + w, startx + b, startx]straight_xs.append(xs)shifted_xs.append([xoffset + x for x in xs])startx += wwhile starty < endy:ys = [starty + p, starty + (3 * p), starty + h, starty + (3 * p), starty + p, starty, starty + p](straight_ys if row % 2 else shifted_ys).append(ys)starty += yoffsetrow += 1polygons = [zip(xs, ys) for xs in shifted_xs for ys in shifted_ys] + [zip(xs, ys) for xs in straight_xs for ys in straight_ys]return polygons

As you predicted, zipping results in much faster performance, especially for larger grids. On my laptop I saw 3x speedup when calculating 30 hexagon grid - 10x speed for 2900 hexagon grid.

>>> from timeit import Timer
>>> t_old = Timer('calc_polygons_orig(1, 1, 100, 100, 10)', 'from hexagons import calc_polygons_orig')
>>> t_new = Timer('calc_polygons_new(1, 1, 100, 100, 10)', 'from hexagons import calc_polygons_new')
>>> t_old.timeit(20000)
9.23395299911499
>>> t_new.timeit(20000)
3.12791109085083
>>> t_old_large = Timer('calc_polygons_orig(1, 1, 1000, 1000, 10)', 'from hexagons import calc_polygons_orig')
>>> t_new_large = Timer('calc_polygons_new(1, 1, 1000, 1000, 10)', 'from hexagons import calc_polygons_new')
>>> t_old_large.timeit(200)
9.09613299369812
>>> t_new_large.timeit(200)
0.7804560661315918

There might be an opportunity to create an iterator rather than the list to save memory. Depends on how your code is using the list of the polygons.

https://en.xdnf.cn/q/71206.html

Related Q&A

Why is -0.0 not the same as 0.0?

I could be missing something fundamental, but consider this interpreter session1:>>> -0.0 is 0.0 False >>> 0.0 is 0.0 True >>> -0.0 # The sign is even retained in the output…

scrapy: exceptions.AttributeError: unicode object has no attribute dont_filter

In scrapy, I am getting the error exceptions.AttributeError: unicode object has no attribute dont_filter. After searching around, I found this answer (which made sense as it was the only bit of code I …

Django Class Based View: Validate object in dispatch

Is there a established way that i validate an object in the dispatch without making an extra database call when self.get_object() is called later in get/post?Here is what i have so far (slightly alter…

Python very slow as compared to Java for this algorithm

Im studying algorithms and decided to port the Java Programs from the textbook to Python, since I dislike the Java overhead, especially for small programs, and as an exercise.The algorithm itself is ve…

fd.seek() IOError: [Errno 22] Invalid argument

My Python Interpreter (v2.6.5) raises the above error in the following codepart:fd = open("some_filename", "r") fd.seek(-2, os.SEEK_END) #same happens if you exchange the second arg…

When is a variable considered constant in terms of PEP8 naming styles?

In keeping with PEP8 conventions, in a .py I can define constants as:NAME = "Me" AGE = "Old" GENER = "Male"If a .txt contained Me Old Male on a single line, and in anothe…

Average Date Array Calculation

I would like to get the mean of the following dates. I thought about converting all the data to seconds and then averaging them. But there is probably a better way to do it.date = [2016-02-23 09:36:26,…

DRF: Serializer Group By Model Field

I want my api to return Account objects grouped by the account_type field in the model. I want to do this so that the grouped accounts are easier to access in my JS code. This is what is returned now:[…

Need help combining two 3 channel images into 6 channel image Python

I am trying to combine two different RGB images into a single 6 channel image (Tiff is best) using nothing but Python.What I have is an RGB image taken from a normal camera as well as another RGB image…

Komodo Edit disable autocomple

I am using Komodo Edit 8 and its autocomplete feature is totally annoying. As soon as I type "for i" , it autofills in this:for i in range:codeNow i have to delete it manually to continue typ…