Skip to content

Commit 0e0f697

Browse files
authored
Merge pull request #5 from ni1o1/0.2.3
0.2.3
2 parents 4e4e78f + 27428ae commit 0e0f697

File tree

9 files changed

+401
-49
lines changed

9 files changed

+401
-49
lines changed

docs/source/bdshadow.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Building shadow
99
Shadow from sunlight
1010
--------------------------------------
1111

12-
.. function:: pybdshadow.bdshadow_sunlight(buildings, date, merge=False, height='height', ground=0)
12+
.. function:: pybdshadow.bdshadow_sunlight(buildings, date, height='height', roof=False,include_building = True,ground=0)
1313

1414
Calculate the sunlight shadow of the buildings.
1515

@@ -19,10 +19,12 @@ buildings : GeoDataFrame
1919
Buildings. coordinate system should be WGS84
2020
date : datetime
2121
Datetime
22-
merge : bool
23-
whether to merge the wall shadows into the building shadows
2422
height : string
2523
Column name of building height
24+
roof : bool
25+
whether to calculate the roof shadows
26+
include_building : bool
27+
whether the shadow include building outline
2628
ground : number
2729
Height of the ground
2830

docs/source/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
author = 'Qing Yu'
2323

2424
# The full version, including alpha/beta/rc tags
25-
release = '0.2.2'
26-
version = '0.2.2'
25+
release = '0.2.3'
26+
version = '0.2.3'
2727
html_logo = "_static/logo-wordmark-light.png"
2828
html_favicon = '_static/logo.ico'
2929
# -- General configuration ---------------------------------------------------

example/example-roof.ipynb

Lines changed: 103 additions & 0 deletions
Large diffs are not rendered by default.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="pybdshadow",
8-
version="0.2.2",
8+
version="0.2.3",
99
author="Qing Yu",
1010
author_email="qingyu0815@foxmail.com",
1111
description="Python package to generate building shadow geometry",

src/pybdshadow/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3333
"""
3434

35-
__version__ = '0.2.2'
35+
__version__ = '0.2.3'
3636
__author__ = 'Qing Yu <qingyu0815@foxmail.com>'
3737

3838
# module level doc-string

src/pybdshadow/preprocess.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,55 @@ def bd_preprocess(buildings, height='height'):
7171
allbds = pd.concat(allbds)
7272
allbds['building_id'] = range(len(allbds))
7373
return allbds
74+
75+
def gdf_difference(gdf_a,gdf_b,col = 'building_id'):
76+
'''
77+
difference gdf_b from gdf_a
78+
'''
79+
gdfa = gdf_a.copy()
80+
gdfb = gdf_b.copy()
81+
gdfb = gdfb[['geometry']]
82+
#判断重叠
83+
from shapely.geometry import MultiPolygon
84+
gdfa.crs = gdfb.crs
85+
gdfb = gpd.sjoin(gdfb,gdfa).groupby([col])['geometry'].apply(
86+
lambda df: MultiPolygon(list(df)).buffer(0)).reset_index()
87+
#分割有重叠和无重叠的
88+
gdfb['tmp'] = 1
89+
gdfa_1 = pd.merge(gdfa,gdfb[[col,'tmp']],how = 'left')
90+
gdfa = gdfa_1[gdfa_1['tmp'] == 1].drop('tmp',axis = 1)
91+
gdfa_notintersected = gdfa_1[gdfa_1['tmp'].isnull()].drop('tmp',axis = 1)
92+
#对有重叠的进行裁剪
93+
gdfa = gdfa.sort_values(by = col).set_index(col)
94+
gdfb = gdfb.sort_values(by = col).set_index(col)
95+
gdfa.crs = gdfb.crs
96+
gdfa['geometry'] = gdfa.difference(gdfb)
97+
gdfa = gdfa.reset_index()
98+
#拼合
99+
gdfa = pd.concat([gdfa,gdfa_notintersected])
100+
return gdfa
101+
102+
def gdf_intersect(gdf_a,gdf_b,col = 'building_id'):
103+
'''
104+
intersect gdf_b from gdf_a
105+
'''
106+
gdfa = gdf_a.copy()
107+
gdfb = gdf_b.copy()
108+
gdfb = gdfb[['geometry']]
109+
#判断重叠
110+
from shapely.geometry import MultiPolygon
111+
gdfa.crs = gdfb.crs
112+
gdfb = gpd.sjoin(gdfb,gdfa).groupby([col])['geometry'].apply(
113+
lambda df: MultiPolygon(list(df)).buffer(0)).reset_index()
114+
#分割有重叠和无重叠的
115+
gdfb['tmp'] = 1
116+
gdfa_1 = pd.merge(gdfa,gdfb[[col,'tmp']],how = 'left')
117+
gdfa = gdfa_1[gdfa_1['tmp'] == 1].drop('tmp',axis = 1)
118+
#对有重叠的进行裁剪
119+
gdfa = gdfa.sort_values(by = col).set_index(col)
120+
gdfb = gdfb.sort_values(by = col).set_index(col)
121+
gdfa.crs = gdfb.crs
122+
gdfa['geometry'] = gdfa.intersection(gdfb)
123+
gdfa = gdfa.reset_index()
124+
125+
return gdfa

src/pybdshadow/pybdshadow.py

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
lonlat_mercator_vector,
4040
mercator_lonlat_vector
4141
)
42+
from .preprocess import gdf_difference,gdf_intersect
4243

4344

4445
def calSunShadow_vector(shape, shapeHeight, sunPosition):
@@ -76,23 +77,27 @@ def calSunShadow_vector(shape, shapeHeight, sunPosition):
7677
return shadowShape
7778

7879

79-
def bdshadow_sunlight(buildings, date, merge=True, height='height', ground=0):
80+
def bdshadow_sunlight(buildings, date, height='height', roof=False,include_building = True,ground=0):
8081
'''
8182
Calculate the sunlight shadow of the buildings.
8283
8384
**Parameters**
85+
8486
buildings : GeoDataFrame
8587
Buildings. coordinate system should be WGS84
8688
date : datetime
8789
Datetime
88-
merge : bool
89-
whether to merge the wall shadows into the building shadows
9090
height : string
9191
Column name of building height
92+
roof : bool
93+
whether to calculate the roof shadows
94+
include_building : bool
95+
whether the shadow include building outline
9296
ground : number
9397
Height of the ground
9498
9599
**Return**
100+
96101
shadows : GeoDataFrame
97102
Building shadow
98103
'''
@@ -126,21 +131,99 @@ def bdshadow_sunlight(buildings, date, merge=True, height='height', ground=0):
126131
walls = walls[['x1', 'y1', 'x2', 'y2', 'building_id', 'height']]
127132
walls['wall'] = walls.apply(lambda r: [[r['x1'], r['y1']],
128133
[r['x2'], r['y2']]], axis=1)
129-
walls_shape = np.array(list(walls['wall']))
134+
135+
ground_shadow = walls.copy()
136+
walls_shape = np.array(list(ground_shadow['wall']))
130137

131138
# calculate shadow for walls
132139
shadowShape = calSunShadow_vector(
133-
walls_shape, walls['height'].values, sunPosition)
134-
135-
walls['geometry'] = list(shadowShape)
136-
walls['geometry'] = walls['geometry'].apply(lambda r: Polygon(r))
137-
walls = gpd.GeoDataFrame(walls)
138-
walls = pd.concat([walls, building])
139-
if merge:
140-
walls = walls.groupby(['building_id'])['geometry'].apply(
141-
lambda df: MultiPolygon(list(df)).buffer(0)).reset_index()
142-
143-
return walls
140+
walls_shape, ground_shadow['height'].values, sunPosition)
141+
142+
ground_shadow['geometry'] = list(shadowShape)
143+
ground_shadow['geometry'] = ground_shadow['geometry'].apply(
144+
lambda r: Polygon(r))
145+
ground_shadow = gpd.GeoDataFrame(ground_shadow)
146+
147+
148+
149+
ground_shadow = pd.concat([ground_shadow, building])
150+
ground_shadow = ground_shadow.groupby(['building_id'])['geometry'].apply(
151+
lambda df: MultiPolygon(list(df)).buffer(0)).reset_index()
152+
153+
ground_shadow['height'] = 0
154+
ground_shadow['type'] = 'ground'
155+
156+
if not roof:
157+
if not include_building:
158+
#从地面阴影裁剪建筑轮廓
159+
ground_shadow = gdf_difference(ground_shadow,buildings)
160+
return ground_shadow
161+
else:
162+
def calwall_shadow(walldata, building):
163+
walls = walldata.copy()
164+
walls_shape = np.array(list(walls['wall']))
165+
# calculate shadow for walls
166+
shadowShape = calSunShadow_vector(
167+
walls_shape, walls['height'].values, sunPosition)
168+
walls['geometry'] = list(shadowShape)
169+
walls['geometry'] = walls['geometry'].apply(lambda r: Polygon(r))
170+
walls = gpd.GeoDataFrame(walls)
171+
walls = pd.concat([walls, building])
172+
173+
walls = walls.groupby(['building_id'])['geometry'].apply(
174+
lambda df: MultiPolygon(list(df)).buffer(0)).reset_index()
175+
return walls
176+
177+
# 计算屋顶阴影
178+
roof_shadows = []
179+
for roof_height in walls[height].drop_duplicates():
180+
# 高于给定高度的墙
181+
walls_high = walls[walls[height] > roof_height].copy()
182+
if len(walls_high) == 0:
183+
continue
184+
walls_high[height] -= roof_height
185+
# 高于给定高度的建筑
186+
building_high = building[building[height] > roof_height].copy()
187+
if len(building_high) == 0:
188+
continue
189+
building_high[height] -= roof_height
190+
# 所有建筑在此高度的阴影
191+
building_shadow_height = calwall_shadow(walls_high, building_high)
192+
# 在此高度的建筑屋顶
193+
building_roof = building[building[height] == roof_height].copy()
194+
building_shadow_height.crs = building_roof.crs
195+
# 取有遮挡的阴影
196+
building_shadow_height = gpd.sjoin(
197+
building_shadow_height, building_roof)
198+
if len(building_shadow_height) == 0:
199+
continue
200+
# 与屋顶做交集
201+
building_roof = gdf_intersect(building_roof,building_shadow_height)
202+
203+
# 再减去这个高度以上的建筑
204+
building_higher = building[building[height] > roof_height].copy()
205+
building_roof = gdf_difference(building_roof,building_higher)
206+
207+
#给出高度信息
208+
building_roof['height'] = roof_height
209+
building_roof = building_roof[-building_roof['geometry'].is_empty]
210+
211+
roof_shadows.append(building_roof)
212+
if len(roof_shadows) == 0:
213+
roof_shadow = gpd.GeoDataFrame()
214+
else:
215+
roof_shadow = pd.concat(roof_shadows)[
216+
['height', 'building_id', 'geometry']]
217+
roof_shadow['type'] = 'roof'
218+
219+
if not include_building:
220+
#从地面阴影裁剪建筑轮廓
221+
ground_shadow = gdf_difference(ground_shadow,buildings)
222+
223+
shadows = pd.concat([roof_shadow, ground_shadow])
224+
shadows.crs = None
225+
shadows['geometry'] = shadows.buffer(0.000001).buffer(-0.000001)
226+
return shadows
144227

145228

146229
def calPointLightShadow_vector(shape, shapeHeight, pointLight):
@@ -183,6 +266,7 @@ def bdshadow_pointlight(buildings,
183266
Calculate the sunlight shadow of the buildings.
184267
185268
**Parameters**
269+
186270
buildings : GeoDataFrame
187271
Buildings. coordinate system should be WGS84
188272
pointlon,pointlat,pointheight : float
@@ -197,6 +281,7 @@ def bdshadow_pointlight(buildings,
197281
Height of the ground
198282
199283
**Return**
284+
200285
shadows : GeoDataFrame
201286
Building shadow
202287
'''

src/pybdshadow/tests/test_pybdshadow.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,49 @@
77

88
class Testpybdshadow:
99
def test_bdshadow_sunlight(self):
10+
1011
buildings = gpd.GeoDataFrame({
11-
'height': [42],
12+
'height': [42, 9],
1213
'geometry': [
1314
Polygon([(139.698311, 35.533796),
14-
(139.698311,
15-
35.533642),
16-
(139.699075,
17-
35.533637),
18-
(139.699079,
19-
35.53417),
20-
(139.698891,
21-
35.53417),
22-
(139.698888,
23-
35.533794),
24-
(139.698311, 35.533796)])]})
25-
date = pd.to_datetime('2015-01-01 02:45:33.959797119')
15+
(139.698311,
16+
35.533642),
17+
(139.699075,
18+
35.533637),
19+
(139.699079,
20+
35.53417),
21+
(139.698891,
22+
35.53417),
23+
(139.698888,
24+
35.533794),
25+
(139.698311, 35.533796)]),
26+
Polygon([(139.69799, 35.534175),
27+
(139.697988, 35.53389),
28+
(139.698814, 35.533885),
29+
(139.698816, 35.534171),
30+
(139.69799, 35.534175)])]})
31+
date = pd.to_datetime('2015-01-01 03:45:33.959797119')
2632

2733
buildings = pybdshadow.bd_preprocess(buildings)
2834

2935
buildingshadow = pybdshadow.bdshadow_sunlight(
30-
buildings, date, merge=True)
36+
buildings, date, roof=True, include_building=False)
3137

3238
area = buildingshadow['geometry'].iloc[0]
3339
area = np.array(area.exterior.coords)
34-
truth = np.array([[139.698311, 35.533796],
35-
[139.69831102, 35.533796],
36-
[139.69831102, 35.533796],
37-
[139.69831239, 35.53429879],
38-
[139.69888939, 35.53429679],
39-
[139.69889239, 35.53467279],
40-
[139.69908039, 35.53467279],
41-
[139.69907902, 35.53417],
42-
[139.69907502, 35.533637],
43-
[139.699075, 35.533637],
44-
[139.699075, 35.533637],
45-
[139.698311, 35.533642],
46-
[139.698311, 35.533796]])
40+
truth = np.array([(139.6983434498457, 35.53388784954066),
41+
(139.698343456533, 35.533887872006716),
42+
(139.6984440527688, 35.53417277873741),
43+
(139.69844406145836, 35.534172800060766),
44+
(139.69844408446318, 35.534172801043766),
45+
(139.69881597541797, 35.534171000119045),
46+
(139.69881599883948, 35.53417099885312),
47+
(139.6988159998281, 35.53417097541834),
48+
(139.69881400017155, 35.533885024533475),
49+
(139.6988139988598, 35.53388500115515),
50+
(139.69881397546646, 35.53388500014851),
51+
(139.69834347324914, 35.53388784822488),
52+
(139.6983434498457, 35.53388784954066)])
4753
assert np.allclose(area, truth)
4854

4955
pybdshadow.show_bdshadow(buildings=buildings,

src/test.ipynb

Lines changed: 104 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)