mirror of
https://github.com/placeAtlas/atlas.git
synced 2025-01-14 00:14:28 +01:00
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:
parent
846de45f8a
commit
d819cfb313
2 changed files with 153 additions and 1 deletions
150
tools/calculate_center.py
Normal file
150
tools/calculate_center.py
Normal 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]
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue