Конвертировать фигуру PNG в KML или GeoJson - PullRequest
1 голос
/ 04 апреля 2020

У меня есть тысячи фигур, сохраненных в виде файлов PNG и координат границ для каждой фигуры. Координаты границ - это координаты 4 углов минимального охватывающего прямоугольника фигуры (пример ниже).

Цель состоит в том, чтобы использовать изображения PNG и координаты их границ для преобразования их в многоугольник (KML или Geo * 1047). *).

Я не уверен даже в тех технологиях, которые могу использовать для достижения результата, поэтому буду признателен за любые предложения.

Входные данные (PNG):

Alt text

  • Координаты 4 углов минимального охватывающего прямоугольника формы: 8.348236, 44.66804, 8.305321, 44.66829, 8.348579, 44.63507, 8.305492, 44.63507.

Желаемый результат:

  • Полигон - это Gist, который показывает результат интерпретации заполненной области PNG, расположенной в нужном месте на карте. Нажмите на Показать исходный BLOB-объект , чтобы увидеть необработанный Geo JSON.

Как мне представить процесс:

  • Шаг 1: у нас есть изображение PNG и 4 очка. Это позволит нам разместить изображение PNG на карте в нужном месте и соответствующим образом масштабировать.
  • Шаг 2: мы узнаем расположение ключевых точек фигуры.
  • Шаг 3: мы извлекаем набор распознанных точек в многоугольник.

Alt text

Я использовал простой PNG в качестве примера, но формы могли быть гораздо сложнее:

Alt text

1 Ответ

1 голос
/ 04 апреля 2020

Хорошо, я сохранил ваше изображение как "shape.png", а ваш Geo JSON в виде прямоугольника "boundaries.json". Тогда мой метод таков:

  • получить север, восток, юг и запад по широте и долготе
  • загрузить и обрезать изображение формы, чтобы избавиться от черного границы, пороговые значения для чистого черного и белого
  • определяют масштабирование по осям 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)

Вот обрезанное изображение:

enter image description here

Вот отладочный вывод:

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
...