将 PNG 形状转换为 KML 或 GeoJson
Convert PNG shape into KML or GeoJson
我有数千个形状存储为 PNG 文件和每个形状的边界坐标。边界坐标是形状的最小外接矩形的 4 个角的坐标(下例)。
目标是使用 PNG 图像及其边界坐标将它们转换为多边形(KML 或 GeoJSON)。
我什至不确定我可以使用哪些技术来达到这个结果,所以我很感激任何建议。
输入数据(PNG):
- 图形最小外接矩形4个角的坐标:
8.348236, 44.66804
, 8.305321, 44.66829
, 8.348579, 44.63507
, 8.305492, 44.63507
.
期望的输出:
- Polygon is a Gist that shows the result of interpreting the filled area of the PNG located in the right place on the map. Click on the Display the source blob 查看原始 GeoJSON。
我怎么想象这个过程:
- 第 1 步:我们有一个 PNG 图像和 4 个点。这让我们可以将 PNG 图像放置在地图上的正确位置并适当地缩放它。
- 第二步:识别形状关键点的位置。
- 第 3 步:我们将一组已识别的点提取到多边形中。
我以简单的 PNG 为例,但形状可能要复杂得多:
好的,我将您的图像保存为 "shape.png"
,将您的 GeoJSON 外接矩形保存为 "boundaries.json"
。那么我的方法如下:
- 根据纬度和经度获取东西南北限制
- 加载并trim形状图像以去除所有黑边,阈值为纯黑白
- 通过查看以像素和度为单位的图像宽度和高度,计算出从像素到度的 X 和 Y 缩放比例
- 使用OpenCV
findContours()
寻找形状图像中的顶点
- 将我找到的所有顶点从图像坐标转换为纬度、经度
- 将这些点写到 JSON 结果文件中。
#!/usr/bin/env python3
import cv2
import json
import geojson
import numpy as np
from geojson import Feature, Point, FeatureCollection, Polygon, dump
def getNESWextents(GeoJSONfile):
# Load the enclosing rectangle JSON
with open('boundaries.json','r') as datafile:
data = json.load(datafile)
feature_collection = FeatureCollection(data['features'])
lats = []
lons = []
for feature in data['features']:
coords = feature['geometry']['coordinates']
lons.append(coords[0])
lats.append(coords[1])
# Work out N, E, S, W extents of boundaries
Nextent = max(lats)
Sextent = min(lats)
Wextent = min(lons)
Eextent = max(lons)
return Nextent, Eextent, Sextent, Wextent
def loadAndTrimImage(imagefilename):
"""Loads the named image and trims it to the extent of its content"""
# Open shape image and extract alpha channel
im = cv2.imread(imagefilename,cv2.IMREAD_UNCHANGED)
alpha = im[...,3]
# Find where non-zero, i.e. not black
y_nonzero, x_nonzero = np.nonzero(alpha)
# Crop to extent of non-black pixels and return
res = alpha[np.min(y_nonzero):np.max(y_nonzero), np.min(x_nonzero):np.max(x_nonzero)]
# Threshold to pure white on black
_, res = cv2.threshold(res, 64, 255, cv2.THRESH_BINARY)
return res
def getVertices(im):
"""Gets the vertices of the shape in im"""
_, contours, *_ = cv2.findContours(im, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Should probably sort by contour area here - and take contour with largest area
perim = cv2.arcLength(contours[0], True)
approx = cv2.approxPolyDP(contours[0], 0.01 * perim, True)
print(f'DEBUG: Found shape with {approx.shape[0]} vertices')
return approx
if __name__ == "__main__":
# Get N, E, S, W extents from JSON file
Nextent, Eextent, Sextent, Wextent = getNESWextents('boundaries.json')
print(f'DEBUG: Nextent={Nextent}, Eextent={Eextent}, Sextent={Sextent}, Wextent={Wextent}')
# Load the image and crop to contents
im = loadAndTrimImage('shape.png')
print('DEBUG: Trimmed image is "trimmed.png"')
cv2.imwrite('trimmed.png', im)
# Get width and height in pixels
Hpx, Wpx = im.shape
# Get width and height in degrees
Hdeg, Wdeg = Nextent-Sextent, Eextent-Wextent
# Calculate degrees per pixel in East-West and North-South direction
degppEW = Wdeg/Wpx
degppNS = Hdeg/Hpx
print(f'DEBUG: degppEW={degppEW}, degppNS={degppNS}')
# Get vertices of shape and stuff into list of features
features = []
vertices = getVertices(im)
for i in range(vertices.shape[0]):
x, y = vertices[i,0]
lon = Wextent + x*degppEW
lat = Nextent - y*degppNS
print(f'DEBUG: Vertex {i}: imageX={x}, imageY={y}, lon={lon}, lat={lat}')
point = Point((lon,lat))
features.append(Feature(geometry=point, properties={"key":"value"}))
# Convert list of features into a FeatureCollection and write to disk
featureCol = FeatureCollection(features)
with open ('result.json', 'w') as f:
dump(featureCol, f)
这是 trimmed 图片:
调试输出如下:
DEBUG: Nextent=44.66828662253787, Eextent=8.348579406738281, Sextent=44.63507036301143, Wextent=8.305320739746094
DEBUG: Trimmed image is "trimmed.png"
DEBUG: degppEW=8.634464469498503e-05, degppNS=6.0503204966194347e-05
DEBUG: Found shape with 6 vertices
DEBUG: Vertex 0: imageX=211, imageY=2, lon=8.323539459776736, lat=44.668165616127936
DEBUG: Vertex 1: imageX=2, imageY=224, lon=8.305493429035483, lat=44.654733904625445
DEBUG: Vertex 2: imageX=81, imageY=472, lon=8.312314655966388, lat=44.63972910979383
DEBUG: Vertex 3: imageX=374, imageY=548, lon=8.337613636862018, lat=44.63513086621639
DEBUG: Vertex 4: imageX=500, imageY=392, lon=8.348493062093587, lat=44.64456936619112
DEBUG: Vertex 5: imageX=484, imageY=155, lon=8.347111547778466, lat=44.65890862576811
我有数千个形状存储为 PNG 文件和每个形状的边界坐标。边界坐标是形状的最小外接矩形的 4 个角的坐标(下例)。
目标是使用 PNG 图像及其边界坐标将它们转换为多边形(KML 或 GeoJSON)。
我什至不确定我可以使用哪些技术来达到这个结果,所以我很感激任何建议。
输入数据(PNG):
- 图形最小外接矩形4个角的坐标:
8.348236, 44.66804
,8.305321, 44.66829
,8.348579, 44.63507
,8.305492, 44.63507
.
期望的输出:
- Polygon is a Gist that shows the result of interpreting the filled area of the PNG located in the right place on the map. Click on the Display the source blob 查看原始 GeoJSON。
我怎么想象这个过程:
- 第 1 步:我们有一个 PNG 图像和 4 个点。这让我们可以将 PNG 图像放置在地图上的正确位置并适当地缩放它。
- 第二步:识别形状关键点的位置。
- 第 3 步:我们将一组已识别的点提取到多边形中。
我以简单的 PNG 为例,但形状可能要复杂得多:
好的,我将您的图像保存为 "shape.png"
,将您的 GeoJSON 外接矩形保存为 "boundaries.json"
。那么我的方法如下:
- 根据纬度和经度获取东西南北限制
- 加载并trim形状图像以去除所有黑边,阈值为纯黑白
- 通过查看以像素和度为单位的图像宽度和高度,计算出从像素到度的 X 和 Y 缩放比例
- 使用OpenCV
findContours()
寻找形状图像中的顶点 - 将我找到的所有顶点从图像坐标转换为纬度、经度
- 将这些点写到 JSON 结果文件中。
#!/usr/bin/env python3
import cv2
import json
import geojson
import numpy as np
from geojson import Feature, Point, FeatureCollection, Polygon, dump
def getNESWextents(GeoJSONfile):
# Load the enclosing rectangle JSON
with open('boundaries.json','r') as datafile:
data = json.load(datafile)
feature_collection = FeatureCollection(data['features'])
lats = []
lons = []
for feature in data['features']:
coords = feature['geometry']['coordinates']
lons.append(coords[0])
lats.append(coords[1])
# Work out N, E, S, W extents of boundaries
Nextent = max(lats)
Sextent = min(lats)
Wextent = min(lons)
Eextent = max(lons)
return Nextent, Eextent, Sextent, Wextent
def loadAndTrimImage(imagefilename):
"""Loads the named image and trims it to the extent of its content"""
# Open shape image and extract alpha channel
im = cv2.imread(imagefilename,cv2.IMREAD_UNCHANGED)
alpha = im[...,3]
# Find where non-zero, i.e. not black
y_nonzero, x_nonzero = np.nonzero(alpha)
# Crop to extent of non-black pixels and return
res = alpha[np.min(y_nonzero):np.max(y_nonzero), np.min(x_nonzero):np.max(x_nonzero)]
# Threshold to pure white on black
_, res = cv2.threshold(res, 64, 255, cv2.THRESH_BINARY)
return res
def getVertices(im):
"""Gets the vertices of the shape in im"""
_, contours, *_ = cv2.findContours(im, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Should probably sort by contour area here - and take contour with largest area
perim = cv2.arcLength(contours[0], True)
approx = cv2.approxPolyDP(contours[0], 0.01 * perim, True)
print(f'DEBUG: Found shape with {approx.shape[0]} vertices')
return approx
if __name__ == "__main__":
# Get N, E, S, W extents from JSON file
Nextent, Eextent, Sextent, Wextent = getNESWextents('boundaries.json')
print(f'DEBUG: Nextent={Nextent}, Eextent={Eextent}, Sextent={Sextent}, Wextent={Wextent}')
# Load the image and crop to contents
im = loadAndTrimImage('shape.png')
print('DEBUG: Trimmed image is "trimmed.png"')
cv2.imwrite('trimmed.png', im)
# Get width and height in pixels
Hpx, Wpx = im.shape
# Get width and height in degrees
Hdeg, Wdeg = Nextent-Sextent, Eextent-Wextent
# Calculate degrees per pixel in East-West and North-South direction
degppEW = Wdeg/Wpx
degppNS = Hdeg/Hpx
print(f'DEBUG: degppEW={degppEW}, degppNS={degppNS}')
# Get vertices of shape and stuff into list of features
features = []
vertices = getVertices(im)
for i in range(vertices.shape[0]):
x, y = vertices[i,0]
lon = Wextent + x*degppEW
lat = Nextent - y*degppNS
print(f'DEBUG: Vertex {i}: imageX={x}, imageY={y}, lon={lon}, lat={lat}')
point = Point((lon,lat))
features.append(Feature(geometry=point, properties={"key":"value"}))
# Convert list of features into a FeatureCollection and write to disk
featureCol = FeatureCollection(features)
with open ('result.json', 'w') as f:
dump(featureCol, f)
这是 trimmed 图片:
调试输出如下:
DEBUG: Nextent=44.66828662253787, Eextent=8.348579406738281, Sextent=44.63507036301143, Wextent=8.305320739746094
DEBUG: Trimmed image is "trimmed.png"
DEBUG: degppEW=8.634464469498503e-05, degppNS=6.0503204966194347e-05
DEBUG: Found shape with 6 vertices
DEBUG: Vertex 0: imageX=211, imageY=2, lon=8.323539459776736, lat=44.668165616127936
DEBUG: Vertex 1: imageX=2, imageY=224, lon=8.305493429035483, lat=44.654733904625445
DEBUG: Vertex 2: imageX=81, imageY=472, lon=8.312314655966388, lat=44.63972910979383
DEBUG: Vertex 3: imageX=374, imageY=548, lon=8.337613636862018, lat=44.63513086621639
DEBUG: Vertex 4: imageX=500, imageY=392, lon=8.348493062093587, lat=44.64456936619112
DEBUG: Vertex 5: imageX=484, imageY=155, lon=8.347111547778466, lat=44.65890862576811