将 PNG 形状转换为 KML 或 GeoJson

Convert PNG shape into KML or GeoJson

我有数千个形状存储为 PNG 文件和每个形状的边界坐标。边界坐标是形状的最小外接矩形的 4 个角的坐标(下例)。

目标是使用 PNG 图像及其边界坐标将它们转换为多边形(KML 或 GeoJSON)。

我什至不确定我可以使用哪些技术来达到这个结果,所以我很感激任何建议。

输入数据(PNG):

期望的输出:

我怎么想象这个过程:

我以简单的 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