Changed the algorithm to calculate the center

this is the version from Mapbox, just to see how well it performs. It
definitely needs improvements as of now. I'm going to include the
atlas.json, but it will be excluded once the pr is ready to be merged
This commit is contained in:
Fabian Wunsch 2022-04-11 16:01:38 +02:00
parent 846de45f8a
commit d819cfb313
2 changed files with 153 additions and 1 deletions

150
tools/calculate_center.py Normal file
View file

@ -0,0 +1,150 @@
"""
From https://github.com/stefda/polylabel,
which is in turn implemented from https://github.com/mapbox/polylabel
"""
from queue import Queue
import math
SQRT2 = math.sqrt(2)
def polylabel(polygon, precision=0.5, debug=False):
"""
Computes the pole of inaccessibility coordinate in [x, y] format.
"""
# find the bounding box of the outer ring
minX = maxX = polygon[0][0]
minY = maxY = polygon[0][1]
for p in polygon[1:]:
if p[0] < minX: minX = p[0]
if p[1] < minY: minY = p[1]
if p[0] > maxX: maxX = p[0]
if p[1] > maxY: maxY = p[1]
width = maxX - minX
height = maxY - minY
cellSize = min(width, height)
h = cellSize / 2.0
# priority queue of cells in order of their "potential" (max distance to polygon)
cellQueue = Queue(0)
if cellSize == 0: return [minX, minY];
# cover polygon with initial cells
for x in range(math.floor(minX), math.ceil(maxX - 1), round(cellSize)):
for y in range(math.floor(minY), math.ceil(maxY - 1), round(cellSize)):
cellQueue.put(Cell(x + h, y + h, h, polygon))
# take centroid as the first best guess
bestCell = getCentroidCell(polygon)
# special case for rectangular polygons
bboxCell = Cell(minX + width / 2, minY + height / 2, 0, polygon)
if bboxCell.d > bestCell.d: bestCell = bboxCell
numProbes = cellQueue.qsize()
while not cellQueue.empty():
# pick the most promising cell from the queue
cell = cellQueue.get()
# update the best cell if we found a better one
if cell.d > bestCell.d:
bestCell = cell
if debug: print('found best %d after %d probes' % (round(1e4 * cell.d) / 1e4, numProbes))
# do not drill down further if there's no chance of a better solution
if cell.max - bestCell.d <= precision: continue
# split the cell into four cells
h = cell.h / 2
cellQueue.put(Cell(cell.x - h, cell.y - h, h, polygon))
cellQueue.put(Cell(cell.x + h, cell.y - h, h, polygon))
cellQueue.put(Cell(cell.x - h, cell.y + h, h, polygon))
cellQueue.put(Cell(cell.x + h, cell.y + h, h, polygon))
numProbes += 4
if debug:
print('num probes: %d' % numProbes)
print('best distance: %d' % bestCell.d)
return [bestCell.x, bestCell.y]
def compareMax(a, b):
return b.max - a.max
class Cell:
def __init__(self, x, y, h, polygon):
self.x = x # cell center x
self.y = y # cell center y
self.h = h # half the cell size
self.d = pointToPolygonDist(x, y, polygon) # distance from cell center to polygon
self.max = self.d + self.h * SQRT2 # max distance to polygon within a cell
def pointToPolygonDist(x, y, polygon):
"""
Computes the signed distance from point to polygon outline (negative if point is outside).
"""
inside = False
minDistSq = float('inf')
for a, b in zip(polygon, rotate(polygon)):
if ((a[1] > y) != (b[1] > y)) and (x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0]):
inside = not inside
minDistSq = min(minDistSq, getSegDistSq(x, y, a, b))
return math.sqrt(minDistSq) * (1 if inside else -1)
def getCentroidCell(polygon):
"""
Gets the polygon centroid.
"""
area = 0
x = 0
y = 0
for a, b in zip(polygon, rotate(polygon)):
f = a[0] * b[1] - b[0] * a[1]
x += (a[0] + b[0]) * f
y += (a[1] + b[1]) * f
area += f * 3
if area == 0:
return Cell(polygon[0][0], polygon[0][1], 0, polygon)
return Cell(x / area, y / area, 0, polygon)
def getSegDistSq(px, py, a, b):
"""
Gets the squared distance from a point to a segment.
"""
x = a[0]
y = a[1]
dx = b[0] - x
dy = b[1] - y
if dx != 0 or dy != 0:
t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy)
if t > 1:
x = b[0]
y = b[1]
elif t > 0:
x += dx * t
y += dy * t
dx = px - x
dy = py - y
return dx * dx + dy * dy
def rotate(l):
return l[-1:] + l[:-1]

View file

@ -3,6 +3,8 @@
import re
import json
from calculate_center import polylabel
"""
Examples:
1. - /r/place
@ -241,7 +243,7 @@ def update_center(entry: dict):
return entry
path = entry['path']
if len(path) > 1:
calculated_center = calculate_center(path)
calculated_center = polylabel(path)
if 'center' not in entry or entry['center'] != calculated_center:
entry['center'] = calculated_center
return entry