参考 求任意多边形内部水平方向似最大矩形算法实现_nullpo_的博客
原文是用 grooovy代码写的,不太好懂,这里把主要逻辑改成python版,测了下效果还行。
代码如下: )
import math
import pandas as pd
import numpy as np
from collections import deque
import matplotlib.pyplot as plt
from numba import jit
class Rectangle:
"""
矩形范围(包围盒).
"""
def __init__(self, minLon, maxLon, minLat, maxLat):
self.minLon = minLon
self.maxLon = maxLon
self.minLat = minLat
self.maxLat = maxLat
def myceil(d, scale):
"""
scale 表示精度如0.1, 0.01等
"""
n = int(1 / scale)
return math.ceil(d * n) / n
def myfloor(d, scale):
n = int(1 / scale)
return math.floor(d * n) / n
def min_enclosing_rectangle(pt_list):
"""
获取多边形区域的最小外接矩形.
"""
rec = Rectangle(
minLon=float('inf'), maxLon=float('-inf'),
minLat=float('inf'), maxLat=float('-inf')
)
pts = np.array(pt_list)
rec.minLon = np.min(pts[:, 0])
rec.maxLon = np.max(pts[:, 0])
rec.minLat = np.min(pts[:, 1])
rec.maxLat = np.max(pts[:, 1])
return rec
def compute_regional_slices(rec, scale):
"""
根据矩形和切分尺度获取切分矩阵的点阵.
为了减少计算量,这里输出的并不是真的矩形,由于是等距连续切分,直接输出切分的数据点即可,以左上角数据点作为标的.
"""
# 1.根据切分尺度标准化矩形
minLon = myfloor(rec.minLon, scale)
maxLon = myceil(rec.maxLon, scale)
minLat = myfloor(rec.minLat, scale)
maxLat = myceil(rec.maxLat, scale)
ndigit = int(math.log10(int(1 / scale))) + 1
# 2.切分(出于人类习惯,暂定从上往下按行切分,即按纬度从大到小,经度从小到大)
matrix = []
for posLat in np.arange(maxLat, minLat, -scale):
row = []
for posLon in np.arange(minLon, maxLon+scale, scale):
row.append([round(posLon, ndigit), round(posLat, ndigit)])
matrix.append(row)
return matrix
@jit(nopython=True)
def PNPoly(vertices, testp):
"""
返回一个点是否在一个多边形区域内(开区间) PNPoly算法
@param vertices: 多边形的顶点
@param testp: 测试点[x, y]
"""
n = len(vertices)
j = n - 1
res = False
for i in range(n):
if (vertices[i][1] > testp[1]) != (vertices[j][1] > testp[1]) and \
testp[0] < (vertices[j][0] - vertices[i][0]) * (testp[1] - vertices[i][1]) / (
vertices[j][1] - vertices[i][1]) + vertices[i][0]:
res = not res
j = i
return res
def isPolygonContainsPoint(pt_list, p):
"""
返回一个点是否在一个多边形区域内(开区间).
"""
nCross = 0
for i in range(len(pt_list)):
p1 = pt_list[i]
p2 = pt_list[(i + 1) % len(pt_list)]
if p1[1] == p2[1]:
continue
if p[1] < min(p1[1], p2[1]) or p[1] >= max(p1[1], p2[1]):
continue
x = p1[0] + (p2[0] - p1[0]) * (p[1] - p1[1]) / (p2[1] - p1[1])
if x > p[0]:
nCross += 1
return nCross % 2 == 1
def compute_mark_matrix(polygon, regionalSlices):
"""
根据切分矩形和多边形计算出标记矩阵.
"""
m = len(regionalSlices)
n = len(regionalSlices[0])
rectangleMarks = [[1] * (n - 1) for _ in range(m - 1)]
def inRange(num, min, max):
return num >= min and num <= max
for posM in range(m):
print(f'mark {posM}')
for posN in range(n):
p = regionalSlices[posM][posN]
if not PNPoly(polygon, p):
if inRange(posM - 1, 0, m - 2) and inRange(posN - 1, 0, n - 2):
rectangleMarks[posM - 1][posN - 1] = 0
if inRange(posM - 1, 0, m - 2) and inRange(posN, 0, n - 2):
rectangleMarks[posM - 1][posN] = 0
if inRange(posM, 0, m - 2) and inRange(posN - 1, 0, n - 2):
rectangleMarks[posM][posN - 1] = 0
if inRange(posM, 0, m - 2) and inRange(posN, 0, n - 2):
rectangleMarks[posM][posN] = 0
return rectangleMarks
def maximal_rectangle(matrix):
"""
根据标记矩阵求最大矩形,返回【最小行标 最大行标 最小列标 最大列标 最大面积】
"""
m = len(matrix)
n = len(matrix[0])
left = [[0] * n for _ in range(m)]
for i in range(m):
for j in range(n):
if matrix[i][j] == 1:
left[i][j] = (left[i][j-1] if j else 0) + 1
min_c, max_c, min_r, max_r, ret = -1, -1, -1, -1, 0
# 对于每一列,使用基于柱状图的方法
for j in range(n):
up = [0] * m
down = [0] * m
que = deque()
for i in range(m):
while len(que) > 0 and left[que[-1]][j] >= left[i][j]:
que.pop()
up[i] = que[-1] if que else -1
que.append(i)
que.clear()
for i in range(m-1, -1, -1):
while que and left[que[-1]][j] >= left[i][j]:
que.pop()
down[i] = que[-1] if que else m
que.append(i)
for i in range(m):
height = down[i] - up[i] - 1
area = height * left[i][j]
if area > ret:
ret = area
min_c = up[i] + 1
max_c = down[i] - 1
min_r = j - left[i][j] + 1
max_r = j
return min_c, max_c, min_r, max_r, ret
def largest_internal_rectangle(polygon):
"""
求一个多边形区域的水平方向最大内接矩形,由于是经纬度数据,精确到小数点后两位,误差(只小不大)约一公里.
"""
scale = 0.01
# 1.区域切块,不是真的切成矩形,而是切分成数据点
min_enclosing_rect = min_enclosing_rectangle(polygon)
# 2.标记矩阵,这里将点阵经纬度转换为矩形标记矩阵,每个矩形以左上角作为标的,
# 比如矩形marks[0][0]的左上角坐标为regionalSlices[0][0],右下角坐标为regionalSlices[1][1]
regional_slices = compute_regional_slices(min_enclosing_rect, scale)
marks = compute_mark_matrix(polygon, regional_slices)
# 3.计算最大内接矩阵,返回矩形
min_c, max_c, min_r, max_r, area = maximal_rectangle(marks)
minLon = regional_slices[0][min_r][0]
maxLon = regional_slices[0][max_r+1][0]
minLat = regional_slices[max_c+1][0][1]
maxLat = regional_slices[min_c][0][1]
return Rectangle(minLon, maxLon, minLat, maxLat)
def largest_internal_rectangle_recursion(polygon, minArea=64):
scale = 0.01
rect_list = []
# 1.区域切块,不是真的切成矩形,而是切分成数据点
min_enclosing_rect = min_enclosing_rectangle(polygon)
# 2.标记矩阵
regional_slices = compute_regional_slices(min_enclosing_rect, scale)
marks = compute_mark_matrix(polygon, regional_slices)
# 3. 把最大矩形的mark置零,剩余部分重新计算出最大矩形,直到面积小于minArea
while True:
min_c, max_c, min_r, max_r, area = maximal_rectangle(marks)
if area < minArea:
break
minLon = regional_slices[0][min_r][0]
maxLon = regional_slices[0][max_r+1][0]
minLat = regional_slices[max_c+1][0][1]
maxLat = regional_slices[min_c][0][1]
rect = Rectangle(minLon, maxLon, minLat, maxLat)
for i in range(min_c, max_c+1):
for j in range(min_r, max_r+1):
marks[i][j] = 0
rect_list.append(rect)
return rect_list
def plot_rect(df, rect_list):
# plot edge
plt.scatter(df['lng'], df['lat'], s=5, alpha=0.8)
for rect in rect_list:
points = np.array([[rect.minLon, rect.minLat], [rect.minLon, rect.maxLat],
[rect.maxLon, rect.maxLat], [rect.maxLon,rect.minLat],
[rect.minLon, rect.minLat]
])
plt.plot(*zip(*points), color='r')
plt.show()
if __name__ == '__main__':
df = pd.read_csv('D:/data/edge_points.csv')
point_list = df[['lng', 'lat']].values
ret_rect = largest_internal_rectangle_recursion(point_list, 64)
print('rect size {}'.format(len(ret_rect)))
plot_rect(df, ret_rect)