Shapely: ๊ธฐ๋Šฅ ์š”์ฒญ: STRtree.nearest ๊ธฐ๋Šฅ ์ถ”๊ฐ€

์— ๋งŒ๋“  2019๋…„ 01์›” 07์ผ  ยท  5์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: Toblerity/Shapely

๋ฉ”๋ชจ

Shapely๋Š” ํ˜„์žฌ GEOS 3.4.0 ์ด์ƒ์„ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ GEOS 3.6.0์— GEOSSTRtree_nearest ์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด ๊ธฐ๋Šฅ์„ ์ง์ ‘ ์ถ”๊ฐ€ํ•˜๊ณ  ๋ช‡ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ

GEOS ์†Œ์Šค ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

const GEOSGeometry *
GEOSSTRtree_nearest (geos::index::strtree::STRtree *tree,
                     const geos::geom::Geometry *g)
{
    return GEOSSTRtree_nearest_r( handle, tree, g);
}

const void* GEOSSTRtree_nearest_generic(GEOSSTRtree *tree,
                                        const void* item,
                                        const GEOSGeometry* itemEnvelope,
                                        GEOSDistanceCallback distancefn,
                                        void* userdata)
{
    return GEOSSTRtree_nearest_generic_r( handle, tree, item, itemEnvelope, distancefn, userdata);
}

๊ทธ๋Ÿฐ ๋‹ค์Œ strtree.py, ctypes_declarations.py ๋ฐ test.py์— ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๊ณ  ํ–ˆ์ง€๋งŒ Segmentation fault: 11 ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
strtree.py

from shapely.geometry.base import geom_factory
class STRtree:
    def nearest(self, geom):
        if self._n_geoms == 0:
            return None

        return geom_factory(lgeos.GEOSSTRtree_nearest(self._tree_handle, geom._geom))

ctypes_declarations.py

def prototype(lgeos, geos_version):
    if geos_version >= (3, 6, 0):
        lgeos.GEOSSTRtree_nearest.argtypes = [c_void_p, c_void_p]
        lgeos.GEOSSTRtree_nearest.restype = c_void_p

test.py

from shapely.geometry import Point, Polygon
from shapely.strtree import STRtree

a = []
a.append(Polygon([(1,0),(2,0),(2,1),(1,1)]))
a.append(Polygon([(0,2),(1,2),(1,3),(0,3)]))
a.append(Polygon([(-1.5,0),(-2.5,0),(-2.5,-1),(-1.5,-1)]))
tree = STRtree(a)
tree.nearest(Point(0,0))

๋‚˜๋Š” ๋‚ด ๋ฌธ์ œ๊ฐ€ ctypes์™€ ๊ธฐํ•˜ํ•™ ๊ฐ„์˜ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์— ๊ด€ํ•œ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์—ฌ shared_path ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ๊ตฌํ˜„ํ•˜๋ ค๊ณ  ์‹œ๋„ํ–ˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ๋ญ”๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

from shapely.geometry import LineString
from shapely.geometry.base import geom_factory
from ctypes import CDLL, c_void_p

dll = CDLL('/Users/*****/anaconda3/lib/python3.7/site-packages/shapely/.dylibs/libgeos_c.1.dylib')
shared_path = dll.GEOSSharedPaths
shared_path.restype = c_void_p
shared_path.argtypes = [c_void_p, c_void_p]
g1 = LineString([((0, 0), (1, 1)), ((-1, 0), (1, 0))])
g2 = LineString([((0, 0), (1, 1)), ((-2, 0), (1, 5))])
geom_factory(shared_path(g1._geom, g2._geom))

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

์กฐ์–ธ @sgillies ์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ง€๊ธˆ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
strtree.py

class STRtree:
    def nearest(self, geom):
        if self._n_geoms == 0:
            return None

        envelope = geom.envelope

        def callback(item1, item2, distance, userdata):
            try:
                geom1 = ctypes.cast(item1, ctypes.py_object).value
                geom2 = ctypes.cast(item2, ctypes.py_object).value
                dist = ctypes.cast(distance, ctypes.POINTER(ctypes.c_double))
                lgeos.GEOSDistance(geom1._geom, geom2._geom, dist)
                return 1
            except:
                return 0 

        item = lgeos.GEOSSTRtree_nearest_generic(self._tree_handle, ctypes.py_object(geom), envelope._geom, \
            lgeos.GEOSDistanceCallback(callback), None)
        geom = ctypes.cast(item, ctypes.py_object).value

        return geom

ctypes_declarations.py

def prototype(lgeos, geos_version):
    if geos_version >= (3, 6, 0):
        lgeos.GEOSDistanceCallback = CFUNCTYPE(c_int, c_void_p, c_void_p, c_void_p, c_void_p)

        lgeos.GEOSSTRtree_nearest_generic.argtypes = [
            c_void_p, py_object, c_void_p, lgeos.GEOSDistanceCallback, py_object]
        lgeos.GEOSSTRtree_nearest_generic.restype = c_void_p

test.py

from shapely.geometry import Point, Polygon
from shapely.strtree import STRtree

a = []
a.append(Polygon([(1,0),(2,0),(2,1),(1,1)]))
a.append(Polygon([(0,2),(1,2),(1,3),(0,3)]))
a.append(Polygon([(-1.5,0),(-2.5,0),(-2.5,-1),(-1.5,-1)]))
a.append(Point(0,0.5))
tree = STRtree(a)
pt = tree.nearest(Point(0,0))
print(pt.wkt)

์‚ฐ์ถœ
POINT (0 0.5)

๋ชจ๋“  5 ๋Œ“๊ธ€

@FuriousRococo๋‹˜ , ์ €๋Š” ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์ด์›ƒ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ ์ต์ˆ™ํ•˜์ง€ ์•Š์ง€๋งŒ ๋Œ€๋žต ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉํ–ฅ์œผ๋กœ ๊ฐ€๊ณ  ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ https://github.com/libgeos/geos/blob/master/capi/geos_c.h.in#L1824 ์—์„œ ํŠธ๋ฆฌ์˜ ํ•ญ๋ชฉ์ด Python ๊ฐœ์ฒด์ด๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ฐ˜ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ํ‘œ์‹œ๋ฅผ ๋ด…๋‹ˆ๋‹ค. . https://github.com/libgeos/geos/blob/master/capi/geos_c.h.in#L1850 ์„ ์‹œ๋„ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์กฐ์–ธ @sgillies ์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ง€๊ธˆ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
strtree.py

class STRtree:
    def nearest(self, geom):
        if self._n_geoms == 0:
            return None

        envelope = geom.envelope

        def callback(item1, item2, distance, userdata):
            try:
                geom1 = ctypes.cast(item1, ctypes.py_object).value
                geom2 = ctypes.cast(item2, ctypes.py_object).value
                dist = ctypes.cast(distance, ctypes.POINTER(ctypes.c_double))
                lgeos.GEOSDistance(geom1._geom, geom2._geom, dist)
                return 1
            except:
                return 0 

        item = lgeos.GEOSSTRtree_nearest_generic(self._tree_handle, ctypes.py_object(geom), envelope._geom, \
            lgeos.GEOSDistanceCallback(callback), None)
        geom = ctypes.cast(item, ctypes.py_object).value

        return geom

ctypes_declarations.py

def prototype(lgeos, geos_version):
    if geos_version >= (3, 6, 0):
        lgeos.GEOSDistanceCallback = CFUNCTYPE(c_int, c_void_p, c_void_p, c_void_p, c_void_p)

        lgeos.GEOSSTRtree_nearest_generic.argtypes = [
            c_void_p, py_object, c_void_p, lgeos.GEOSDistanceCallback, py_object]
        lgeos.GEOSSTRtree_nearest_generic.restype = c_void_p

test.py

from shapely.geometry import Point, Polygon
from shapely.strtree import STRtree

a = []
a.append(Polygon([(1,0),(2,0),(2,1),(1,1)]))
a.append(Polygon([(0,2),(1,2),(1,3),(0,3)]))
a.append(Polygon([(-1.5,0),(-2.5,0),(-2.5,-1),(-1.5,-1)]))
a.append(Point(0,0.5))
tree = STRtree(a)
pt = tree.nearest(Point(0,0))
print(pt.wkt)

์‚ฐ์ถœ
POINT (0 0.5)

@FuriousRococo ์ด ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์œผ๋กœ ํ’€ ๋ฆฌํ€˜์ŠคํŠธ๋ฅผ ์ œ์ถœํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

@FuriousRococo ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ธฐ๋Šฅ์„ ์ดํ•ดํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜๋Š” ๋™์•ˆ ์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค. ํ•œ ์ง€์ ์— ๋Œ€ํ•ด ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๋„๋กœ๋ฅผ ์ฐพ์œผ๋ ค๊ณ  ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ STR ํŠธ๋ฆฌ๋Š” MultiLineString ๋„๋กœ์˜ ํŠธ๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ๊ฒฝ์šฐ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๋„๋กœ๋Š” ์ ์—์„œ MultiLineStrings๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ ํˆฌ์˜ ๊ฑฐ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ง€์ •๋ฉ๋‹ˆ๊นŒ? ์•„๋‹ˆ๋ฉด ๋„๋กœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” MultiLineStrings์˜ ์ค‘์‹ฌ๊นŒ์ง€์˜ ๊ฐ€์žฅ ์ž‘์€ ๊ฑฐ๋ฆฌ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๊นŒ? ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ–ˆ๊ณ  ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ํ•จ์ˆ˜๊ฐ€ MultiLineString์˜ ๋ชจ๋“  ์ค„์„ ํ™•์ธํ•˜์—ฌ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์ค„์ด MultiLineString ์•ˆ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ ๋‚ด ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค @asif-rehan
test.py

from shapely.strtree import STRtree
from shapely.geometry import Point, LineString, MultiLineString

tree_list = [LineString([[0,1],[1,1]])]
tree_list.append(MultiLineString([LineString([[0,0],[1,0]]),LineString([[0,3],[1,3]])]))
tree = STRtree(tree_list)

tree.nearest(Point(0.5,0.25)).wkt

์‚ฐ์ถœ
MULTILINESTRING ((0 0, 1 0), (0 3, 1 3))

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰