Skip to main content
replaced http://gis.stackexchange.com/ with https://gis.stackexchange.com/
Source Link

Shapefiles have no type MultiPolygon (type = Polygon), but they support them anyway (all rings are stored in one feature = list of polygons, look at Converting huge multipolygon to polygonsConverting huge multipolygon to polygons)

Fiona interprets the feature as a MultiPolygon and you can apply the solution presented in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (1)More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (1)

Solution 4 with GeoPandas as in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (2)More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (2)

Shapefiles have no type MultiPolygon (type = Polygon), but they support them anyway (all rings are stored in one feature = list of polygons, look at Converting huge multipolygon to polygons)

Fiona interprets the feature as a MultiPolygon and you can apply the solution presented in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (1)

Solution 4 with GeoPandas as in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (2)

Shapefiles have no type MultiPolygon (type = Polygon), but they support them anyway (all rings are stored in one feature = list of polygons, look at Converting huge multipolygon to polygons)

Fiona interprets the feature as a MultiPolygon and you can apply the solution presented in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (1)

Solution 4 with GeoPandas as in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (2)

added 1079 characters in body
Source Link
gene
  • 55.8k
  • 3
  • 115
  • 200
 import fiona  from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon  multimultipol = fiona.open("multipol.shp") multi= multipol = multi.next() # only one feature in the shapefile  print multipolmulti  {'geometry': {'type': 'MultiPolygon', 'coordinates': [[[(-0.5275288092189501, 0.5569782330345711), (-0.11779769526248396, 0.29065300896286816), (-0.25608194622279135, 0.01920614596670933), (-0.709346991037132, -0.08834827144686286), (-0.8629961587708066, 0.18309859154929575), (-0.734955185659411, 0.39820742637644047), (-0.5275288092189501, 0.5569782330345711)]], [[(0.19974391805377723, 0.060179257362355965), (0.5480153649167734, 0.1293213828425096), (0.729833546734955, 0.03969270166453265), (0.8143405889884763, -0.13956466069142115), (0.701664532650448, -0.38540332906530095), (0.4763124199743918, -0.5006402048655569), (0.26888604353393086, -0.4238156209987196), (0.18950064020486557, -0.2291933418693981), (0.19974391805377723, 0.060179257362355965)]], [[(-0.3764404609475033, -0.295774647887324), (-0.11523687580025621, -0.3597951344430217), (-0.033290653008962945, -0.5800256081946222), (-0.11523687580025621, -0.7413572343149808), (-0.3072983354673495, -0.8591549295774648), (-0.58898847631242, -0.6927016645326505), (-0.6555697823303457, -0.4750320102432779), (-0.3764404609475033, -0.295774647887324)]]]}, 'type': 'Feature', 'id': '0', 'properties': OrderedDict([(u'id', 1)])} 
points= ([pt for pt in fiona.open("points.shp")]) for i, pt in enumerate(points): point = shape(pt['geometry']) if point.within(multi['geometry'shape(multi['geometry'])): print i, shape(points[i])['geometry']points[i]['geometry']) 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.4993597951344431 -0.06017925736235585) 5 POINT (-0.3764404609475033 -0.4750320102432779) 6 POINT (-0.3098591549295775 -0.6312419974391805) 

This is a Supplementsupplement to the answer of xulnik.

Solution 3 with ogr and the geo_interface protocol (Python Geo_interface applications)

from osgeo import ogr import json def records(file): # generator reader = ogr.Open(file) layer = reader.GetLayer(0) for i in range(layer.GetFeatureCount()): feature = layer.GetFeature(i) yield json.loads(feature.ExportToJson()) points = [pt for pt in records("point_multi_contains.shp")] multipol = records("multipol.shp") multi = multipol.next() # 1 feature for i, pt in enumerate(points): point = shape(pt['geometry']) if point.within(shape(multi['geometry'])): print i, shape(points[i]['geometry']) 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.499359795134443 -0.060179257362356) 5 POINT (-0.376440460947503 -0.475032010243278) 6 POINT (-0.309859154929577 -0.631241997439181) 

Solution 4 with GeoPandas as in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (2)

 import fiona  from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon  multi = fiona.open("multipol.shp") multipol = multi.next() # only one feature in the shapefile  print multipol  {'geometry': {'type': 'MultiPolygon', 'coordinates': [[[(-0.5275288092189501, 0.5569782330345711), (-0.11779769526248396, 0.29065300896286816), (-0.25608194622279135, 0.01920614596670933), (-0.709346991037132, -0.08834827144686286), (-0.8629961587708066, 0.18309859154929575), (-0.734955185659411, 0.39820742637644047), (-0.5275288092189501, 0.5569782330345711)]], [[(0.19974391805377723, 0.060179257362355965), (0.5480153649167734, 0.1293213828425096), (0.729833546734955, 0.03969270166453265), (0.8143405889884763, -0.13956466069142115), (0.701664532650448, -0.38540332906530095), (0.4763124199743918, -0.5006402048655569), (0.26888604353393086, -0.4238156209987196), (0.18950064020486557, -0.2291933418693981), (0.19974391805377723, 0.060179257362355965)]], [[(-0.3764404609475033, -0.295774647887324), (-0.11523687580025621, -0.3597951344430217), (-0.033290653008962945, -0.5800256081946222), (-0.11523687580025621, -0.7413572343149808), (-0.3072983354673495, -0.8591549295774648), (-0.58898847631242, -0.6927016645326505), (-0.6555697823303457, -0.4750320102432779), (-0.3764404609475033, -0.295774647887324)]]]}, 'type': 'Feature', 'id': '0', 'properties': OrderedDict([(u'id', 1)])} 
for i, pt in enumerate(points): point = shape(pt['geometry']) if point.within(multi['geometry')): print i, shape(points[i])['geometry']) 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.4993597951344431 -0.06017925736235585) 5 POINT (-0.3764404609475033 -0.4750320102432779) 6 POINT (-0.3098591549295775 -0.6312419974391805) 

This is a Supplement to the answer of xulnik.

Solution 3 with GeoPandas as in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (2)

import fiona from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon multipol = fiona.open("multipol.shp") multi= multipol.next() # only one feature in the shapefile print multi {'geometry': {'type': 'MultiPolygon', 'coordinates': [[[(-0.5275288092189501, 0.5569782330345711), (-0.11779769526248396, 0.29065300896286816), (-0.25608194622279135, 0.01920614596670933), (-0.709346991037132, -0.08834827144686286), (-0.8629961587708066, 0.18309859154929575), (-0.734955185659411, 0.39820742637644047), (-0.5275288092189501, 0.5569782330345711)]], [[(0.19974391805377723, 0.060179257362355965), (0.5480153649167734, 0.1293213828425096), (0.729833546734955, 0.03969270166453265), (0.8143405889884763, -0.13956466069142115), (0.701664532650448, -0.38540332906530095), (0.4763124199743918, -0.5006402048655569), (0.26888604353393086, -0.4238156209987196), (0.18950064020486557, -0.2291933418693981), (0.19974391805377723, 0.060179257362355965)]], [[(-0.3764404609475033, -0.295774647887324), (-0.11523687580025621, -0.3597951344430217), (-0.033290653008962945, -0.5800256081946222), (-0.11523687580025621, -0.7413572343149808), (-0.3072983354673495, -0.8591549295774648), (-0.58898847631242, -0.6927016645326505), (-0.6555697823303457, -0.4750320102432779), (-0.3764404609475033, -0.295774647887324)]]]}, 'type': 'Feature', 'id': '0', 'properties': OrderedDict([(u'id', 1)])} 
points= ([pt for pt in fiona.open("points.shp")]) for i, pt in enumerate(points): point = shape(pt['geometry']) if point.within(shape(multi['geometry'])): print i, shape(points[i]['geometry']) 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.4993597951344431 -0.06017925736235585) 5 POINT (-0.3764404609475033 -0.4750320102432779) 6 POINT (-0.3098591549295775 -0.6312419974391805) 

This is a supplement to the answer of xulnik.

Solution 3 with ogr and the geo_interface protocol (Python Geo_interface applications)

from osgeo import ogr import json def records(file): # generator reader = ogr.Open(file) layer = reader.GetLayer(0) for i in range(layer.GetFeatureCount()): feature = layer.GetFeature(i) yield json.loads(feature.ExportToJson()) points = [pt for pt in records("point_multi_contains.shp")] multipol = records("multipol.shp") multi = multipol.next() # 1 feature for i, pt in enumerate(points): point = shape(pt['geometry']) if point.within(shape(multi['geometry'])): print i, shape(points[i]['geometry']) 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.499359795134443 -0.060179257362356) 5 POINT (-0.376440460947503 -0.475032010243278) 6 POINT (-0.309859154929577 -0.631241997439181) 

Solution 4 with GeoPandas as in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (2)

added 812 characters in body
Source Link
gene
  • 55.8k
  • 3
  • 115
  • 200

enter image description hereenter image description here

If I open a MultiPolygon shapefile, the geometry is 'Polygon'

import fiona # creation of a MultiPolygon shapefile polysmultipolys = fiona.open("multipoly"multipol.shp") print len(polys) 3 # 3 polygons in the shapefile polysmultipolys.schema {'geometry': 'Polygon', 'properties': OrderedDict([(u'id', 'int:10')])} # creation of alen(multipolys) 1 

Solution 1 with Fiona

 MultiPolygonimport fiona  from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon  multi = MultiPolygon([shapefiona.open(i['geometry']"multipol.shp") for   imultipol in= polys]multi.next() # schema# ofonly theone newfeature shapefile multin =the polys.schemashapefile mult['geometry'] = 'MultiPolygon' print multmultipol  {'geometry': {'type': 'MultiPolygon', 'properties''coordinates': OrderedDict([[[[(u'id'-0.5275288092189501, 'int:10')]0.5569782330345711)} with, fiona.open("multipol-0.shp"11779769526248396, 'w'0.29065300896286816),driver='ESRI Shapefile'(-0.25608194622279135, schema=mult0.01920614596670933) as output: , output.write({'geometry':-0.709346991037132, mapping(multi-0.08834827144686286), 'properties':{'id':(-0.8629961587708066, 1}}0.18309859154929575) 

Now if I open the new shapefile, the geometry is 'Polygon' but with only one feature (= list of the 3 polygons)

multipolys =, fiona.open("multipol.shp") multipolys-0.schema {'geometry': 'Polygon'734955185659411, 'properties':0.39820742637644047), OrderedDict([(u'id'-0.5275288092189501, 'int:10')])} len(multipolys0.5569782330345711) 1 

Solution 1 expanding the list of polygons:

polygons = [poly for poly in multi for multi in]], multipolys] len[[(polygons) 3 

And now you can apply the solution presented in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (1)

points = [pt for pt in fiona0.open('points19974391805377723, 0.shp'060179257362355965)] # iterate through points for i, pt in enumerate(points): point =0.5480153649167734, shape(pt['geometry']0.1293213828425096) #iterate through polygons for j, poly in enumerate(polygons): 0.729833546734955, 0.03969270166453265), (0.8143405889884763, -0.13956466069142115), if(0.701664532650448, point-0.within(poly38540332906530095): , (0.4763124199743918, -0.5006402048655569), (0.26888604353393086, -0.4238156209987196), (0.18950064020486557, -0.2291933418693981), (0.19974391805377723, 0.060179257362355965)]], print[[(-0.3764404609475033, i-0.295774647887324), shape(points[i]['geometry'])-0.11523687580025621, #-0.3597951344430217), polygons[j] 1(-0.033290653008962945, POINT-0.5800256081946222), (-0.5889884763124211523687580025621, -0.177976952624847413572343149808) 3 POINT, (-0.49935979513444313072983354673495, -0.060179257362355858591549295774648) 5 POINT, (-0.376440460947503358898847631242, -0.47503201024327796927016645326505) 6 POINT, (-0.30985915492957756555697823303457, -0.63124199743918054750320102432779) 

Solution 2 : recreating the MultiPolygon with Fiona

multi =, MultiPolygon([poly-0.3764404609475033, for-0.295774647887324)]]]}, poly'type': in'Feature', multi'id': for'0', multi'properties': inOrderedDict([(u'id', multipolys]1)])} 

Fiona interprets the feature as a MultiPolygon and you can apply the solution presented in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (1)

for i, pt in enumerate(points): point = shape(pt['geometry']) if point.within(multimulti['geometry')): print i, shape(points[i]['geometry']points[i])  ['geometry']) 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.4993597951344431 -0.06017925736235585) 5 POINT (-0.3764404609475033 -0.4750320102432779) 6 POINT (-0.3098591549295775 -0.6312419974391805) 

Solution 32 with pyshp (shapefile) and the geo_interface (GeoJSON like) protocol

This is a Supplement to the answer of xulnik.

import shapefile pts = shapefile.Reader("points.shp") polys = shapefile.Reader("multipol.shp") points = [pt.shape.__geo_interface__ for pt in pts.shapeRecords()] multi = [shapeshape(polpolys.shapeRecords()[0].shape.__geo_interface__) for# pol1 inpolygon print polymulti MULTIPOLYGON (((-0.shapeRecords5275288092189501 0.5569782330345711, -0.117797695262484 0.2906530089628682, -0.2560819462227913 0.01920614596670933, -0.7093469910371319 -0.08834827144686286, -0.8629961587708066 0.1830985915492958, -0.734955185659411 0.3982074263764405, -0.5275288092189501 0.5569782330345711)), ((0.1997439180537772 0.06017925736235596, 0.5480153649167734 0.1293213828425096, 0.729833546734955 0.03969270166453265, 0.8143405889884763 -0.1395646606914211, 0.701664532650448 -0.3854033290653009, 0.4763124199743918 -0.5006402048655569, 0.2688860435339309 -0.4238156209987196, 0.1895006402048656 -0.2291933418693981, 0.1997439180537772 0.06017925736235596)]), ((-0.3764404609475033 -0.295774647887324, -0.1152368758002562 -0.3597951344430217, -0.03329065300896294 -0.5800256081946222, -0.1152368758002562 -0.7413572343149808, -0.3072983354673495 -0.8591549295774648, -0.58898847631242 -0.6927016645326505, -0.6555697823303457 -0.4750320102432779, -0.3764404609475033 -0.295774647887324))) for i, pt in enumerate(points): point = shape(pt) if point.within(multi[0]multi): #first multipolygon print i, shape(points[i]) 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.4993597951344431 -0.06017925736235585) 5 POINT (-0.3764404609475033 -0.4750320102432779) 6 POINT (-0.3098591549295775 -0.6312419974391805) 

Solution 43 with GeoPandas as in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (2)

The points 1,3,5,6 falls within the boundaries of the MultiPolygon

enter image description here

enter image description here

import fiona # creation of a MultiPolygon shapefile polys = fiona.open("multipoly.shp") print len(polys) 3 # 3 polygons in the shapefile polys.schema {'geometry': 'Polygon', 'properties': OrderedDict([(u'id', 'int:10')])} # creation of a MultiPolygon from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon multi = MultiPolygon([shape(i['geometry']) for i in polys]) # schema of the new shapefile mult = polys.schema mult['geometry'] = 'MultiPolygon' print mult {'geometry': 'MultiPolygon', 'properties': OrderedDict([(u'id', 'int:10')])} with fiona.open("multipol.shp", 'w',driver='ESRI Shapefile', schema=mult) as output:  output.write({'geometry': mapping(multi), 'properties':{'id': 1}}) 

Now if I open the new shapefile, the geometry is 'Polygon' but with only one feature (= list of the 3 polygons)

multipolys = fiona.open("multipol.shp") multipolys.schema {'geometry': 'Polygon', 'properties': OrderedDict([(u'id', 'int:10')])} len(multipolys) 1 

Solution 1 expanding the list of polygons:

polygons = [poly for poly in multi for multi in multipolys] len(polygons) 3 

And now you can apply the solution presented in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (1)

points = [pt for pt in fiona.open('points.shp')] # iterate through points for i, pt in enumerate(points): point = shape(pt['geometry']) #iterate through polygons for j, poly in enumerate(polygons):  if point.within(poly):  print i, shape(points[i]['geometry']) #, polygons[j] 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.4993597951344431 -0.06017925736235585) 5 POINT (-0.3764404609475033 -0.4750320102432779) 6 POINT (-0.3098591549295775 -0.6312419974391805) 

Solution 2 : recreating the MultiPolygon with Fiona

multi = MultiPolygon([poly for poly in multi for multi in multipolys]) for i, pt in enumerate(points): point = shape(pt['geometry']) if point.within(multi): print i, shape(points[i]['geometry'])   1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.4993597951344431 -0.06017925736235585) 5 POINT (-0.3764404609475033 -0.4750320102432779) 6 POINT (-0.3098591549295775 -0.6312419974391805) 

Solution 3 with pyshp (shapefile) and the geo_interface protocol

This is a Supplement to the answer of xulnik

pts = shapefile.Reader("points.shp") polys = shapefile.Reader("multipol.shp") points = [pt.shape.__geo_interface__ for pt in pts.shapeRecords()] multi = [shape(pol.shape.__geo_interface__) for pol in poly.shapeRecords()] for i, pt in enumerate(points): point = shape(pt) if point.within(multi[0]): #first multipolygon print i, shape(points[i]) 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.4993597951344431 -0.06017925736235585) 5 POINT (-0.3764404609475033 -0.4750320102432779) 6 POINT (-0.3098591549295775 -0.6312419974391805) 

Solution 4 with GeoPandas as in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (2)

The points 1,3,5,6 falls within the boundaries of the MultiPolygon

enter image description here

enter image description here

If I open a MultiPolygon shapefile, the geometry is 'Polygon'

multipolys = fiona.open("multipol.shp") multipolys.schema {'geometry': 'Polygon', 'properties': OrderedDict([(u'id', 'int:10')])} len(multipolys) 1 

Solution 1 with Fiona

 import fiona  from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon  multi = fiona.open("multipol.shp")   multipol = multi.next() # only one feature in the shapefile print multipol  {'geometry': {'type': 'MultiPolygon', 'coordinates': [[[(-0.5275288092189501, 0.5569782330345711), (-0.11779769526248396, 0.29065300896286816), (-0.25608194622279135, 0.01920614596670933), (-0.709346991037132, -0.08834827144686286), (-0.8629961587708066, 0.18309859154929575), (-0.734955185659411, 0.39820742637644047), (-0.5275288092189501, 0.5569782330345711)]], [[(0.19974391805377723, 0.060179257362355965), (0.5480153649167734, 0.1293213828425096), (0.729833546734955, 0.03969270166453265), (0.8143405889884763, -0.13956466069142115), (0.701664532650448, -0.38540332906530095), (0.4763124199743918, -0.5006402048655569), (0.26888604353393086, -0.4238156209987196), (0.18950064020486557, -0.2291933418693981), (0.19974391805377723, 0.060179257362355965)]], [[(-0.3764404609475033, -0.295774647887324), (-0.11523687580025621, -0.3597951344430217), (-0.033290653008962945, -0.5800256081946222), (-0.11523687580025621, -0.7413572343149808), (-0.3072983354673495, -0.8591549295774648), (-0.58898847631242, -0.6927016645326505), (-0.6555697823303457, -0.4750320102432779), (-0.3764404609475033, -0.295774647887324)]]]}, 'type': 'Feature', 'id': '0', 'properties': OrderedDict([(u'id', 1)])} 

Fiona interprets the feature as a MultiPolygon and you can apply the solution presented in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (1)

for i, pt in enumerate(points): point = shape(pt['geometry']) if point.within(multi['geometry')): print i, shape(points[i])['geometry']) 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.4993597951344431 -0.06017925736235585) 5 POINT (-0.3764404609475033 -0.4750320102432779) 6 POINT (-0.3098591549295775 -0.6312419974391805) 

Solution 2 with pyshp (shapefile) and the geo_interface (GeoJSON like) protocol

This is a Supplement to the answer of xulnik.

import shapefile pts = shapefile.Reader("points.shp") polys = shapefile.Reader("multipol.shp") points = [pt.shape.__geo_interface__ for pt in pts.shapeRecords()] multi = shape(polys.shapeRecords()[0].shape.__geo_interface__) # 1 polygon print multi MULTIPOLYGON (((-0.5275288092189501 0.5569782330345711, -0.117797695262484 0.2906530089628682, -0.2560819462227913 0.01920614596670933, -0.7093469910371319 -0.08834827144686286, -0.8629961587708066 0.1830985915492958, -0.734955185659411 0.3982074263764405, -0.5275288092189501 0.5569782330345711)), ((0.1997439180537772 0.06017925736235596, 0.5480153649167734 0.1293213828425096, 0.729833546734955 0.03969270166453265, 0.8143405889884763 -0.1395646606914211, 0.701664532650448 -0.3854033290653009, 0.4763124199743918 -0.5006402048655569, 0.2688860435339309 -0.4238156209987196, 0.1895006402048656 -0.2291933418693981, 0.1997439180537772 0.06017925736235596)), ((-0.3764404609475033 -0.295774647887324, -0.1152368758002562 -0.3597951344430217, -0.03329065300896294 -0.5800256081946222, -0.1152368758002562 -0.7413572343149808, -0.3072983354673495 -0.8591549295774648, -0.58898847631242 -0.6927016645326505, -0.6555697823303457 -0.4750320102432779, -0.3764404609475033 -0.295774647887324))) for i, pt in enumerate(points): point = shape(pt) if point.within(multi): print i, shape(points[i]) 1 POINT (-0.58898847631242 0.17797695262484) 3 POINT (0.4993597951344431 -0.06017925736235585) 5 POINT (-0.3764404609475033 -0.4750320102432779) 6 POINT (-0.3098591549295775 -0.6312419974391805) 

Solution 3 with GeoPandas as in More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc (2)

The points 1,3,5,6 falls within the boundaries of the MultiPolygon

added 817 characters in body
Source Link
gene
  • 55.8k
  • 3
  • 115
  • 200
Loading
added 18 characters in body
Source Link
gene
  • 55.8k
  • 3
  • 115
  • 200
Loading
added 204 characters in body
Source Link
gene
  • 55.8k
  • 3
  • 115
  • 200
Loading
added 204 characters in body
Source Link
gene
  • 55.8k
  • 3
  • 115
  • 200
Loading
added 902 characters in body
Source Link
gene
  • 55.8k
  • 3
  • 115
  • 200
Loading
edited body
Source Link
gene
  • 55.8k
  • 3
  • 115
  • 200
Loading
Source Link
gene
  • 55.8k
  • 3
  • 115
  • 200
Loading