huff 1.2.0__py3-none-any.whl → 1.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- huff/gistools.py +123 -3
- huff/models.py +134 -30
- huff/ors.py +23 -18
- huff/osm.py +250 -0
- huff/tests/data/Haslach.qmd +43 -43
- huff/tests/data/Haslach_supermarkets.qmd +43 -43
- huff/tests/tests_huff.py +126 -19
- huff-1.3.1.dist-info/METADATA +78 -0
- {huff-1.2.0.dist-info → huff-1.3.1.dist-info}/RECORD +11 -10
- huff-1.2.0.dist-info/METADATA +0 -59
- {huff-1.2.0.dist-info → huff-1.3.1.dist-info}/WHEEL +0 -0
- {huff-1.2.0.dist-info → huff-1.3.1.dist-info}/top_level.txt +0 -0
huff/osm.py
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
#-----------------------------------------------------------------------
|
2
|
+
# Name: osm (huff package)
|
3
|
+
# Purpose: Helper functions for OpenStreetMap API
|
4
|
+
# Author: Thomas Wieland
|
5
|
+
# ORCID: 0000-0001-5168-9846
|
6
|
+
# mail: geowieland@googlemail.com
|
7
|
+
# Version: 1.3.1
|
8
|
+
# Last update: 2025-05-28 18:01
|
9
|
+
# Copyright (c) 2025 Thomas Wieland
|
10
|
+
#-----------------------------------------------------------------------
|
11
|
+
|
12
|
+
|
13
|
+
import pandas as pd
|
14
|
+
import geopandas as gpd
|
15
|
+
import math
|
16
|
+
import requests
|
17
|
+
import tempfile
|
18
|
+
import time
|
19
|
+
import os
|
20
|
+
from PIL import Image
|
21
|
+
import matplotlib.pyplot as plt
|
22
|
+
import contextily as cx
|
23
|
+
from shapely.geometry import box
|
24
|
+
|
25
|
+
|
26
|
+
class Client:
|
27
|
+
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
server = "http://a.tile.openstreetmap.org/",
|
31
|
+
headers = {
|
32
|
+
'User-Agent': 'huff.osm/1.0.0 (your_name@your_email_provider.com)'
|
33
|
+
}
|
34
|
+
):
|
35
|
+
|
36
|
+
self.server = server
|
37
|
+
self.headers = headers
|
38
|
+
|
39
|
+
def download_tile(
|
40
|
+
self,
|
41
|
+
zoom,
|
42
|
+
x,
|
43
|
+
y,
|
44
|
+
timeout = 10
|
45
|
+
):
|
46
|
+
|
47
|
+
osm_url = self.server + f"{zoom}/{x}/{y}.png"
|
48
|
+
|
49
|
+
response = requests.get(
|
50
|
+
osm_url,
|
51
|
+
headers = self.headers,
|
52
|
+
timeout = timeout
|
53
|
+
)
|
54
|
+
|
55
|
+
if response.status_code == 200:
|
56
|
+
|
57
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp_file:
|
58
|
+
tmp_file.write(response.content)
|
59
|
+
tmp_file_path = tmp_file.name
|
60
|
+
return Image.open(tmp_file_path)
|
61
|
+
|
62
|
+
else:
|
63
|
+
|
64
|
+
print(f"Error while accessing OSM server. Status code: {response.status_code} - {response.reason}")
|
65
|
+
|
66
|
+
return None
|
67
|
+
|
68
|
+
|
69
|
+
def get_basemap(
|
70
|
+
sw_lat,
|
71
|
+
sw_lon,
|
72
|
+
ne_lat,
|
73
|
+
ne_lon,
|
74
|
+
zoom = 15
|
75
|
+
):
|
76
|
+
|
77
|
+
def lat_lon_to_tile(
|
78
|
+
lat,
|
79
|
+
lon,
|
80
|
+
zoom
|
81
|
+
):
|
82
|
+
|
83
|
+
n = 2 ** zoom
|
84
|
+
x = int(n * ((lon + 180) / 360))
|
85
|
+
y = int(n * (1 - (math.log(math.tan(math.radians(lat)) + 1 / math.cos(math.radians(lat))) / math.pi)) / 2)
|
86
|
+
return x, y
|
87
|
+
|
88
|
+
def stitch_tiles(
|
89
|
+
zoom,
|
90
|
+
sw_lat,
|
91
|
+
sw_lon,
|
92
|
+
ne_lat,
|
93
|
+
ne_lon,
|
94
|
+
delay = 0.1
|
95
|
+
):
|
96
|
+
|
97
|
+
osm_client = Client(
|
98
|
+
server = "http://a.tile.openstreetmap.org/"
|
99
|
+
)
|
100
|
+
|
101
|
+
sw_x_tile, sw_y_tile = lat_lon_to_tile(sw_lat, sw_lon, zoom)
|
102
|
+
ne_x_tile, ne_y_tile = lat_lon_to_tile(ne_lat, ne_lon, zoom)
|
103
|
+
|
104
|
+
tile_size = 256
|
105
|
+
width = (ne_x_tile - sw_x_tile + 1) * tile_size
|
106
|
+
height = (sw_y_tile - ne_y_tile + 1) * tile_size
|
107
|
+
|
108
|
+
stitched_image = Image.new('RGB', (width, height))
|
109
|
+
|
110
|
+
for x in range(sw_x_tile, ne_x_tile + 1):
|
111
|
+
for y in range(ne_y_tile, sw_y_tile + 1):
|
112
|
+
tile = osm_client.download_tile(
|
113
|
+
zoom = zoom,
|
114
|
+
x = x,
|
115
|
+
y = y
|
116
|
+
)
|
117
|
+
if tile:
|
118
|
+
|
119
|
+
stitched_image.paste(tile, ((x - sw_x_tile) * tile_size, (sw_y_tile - y) * tile_size))
|
120
|
+
else:
|
121
|
+
print(f"Error while retrieving tile {x}, {y}.")
|
122
|
+
|
123
|
+
time.sleep(delay)
|
124
|
+
|
125
|
+
return stitched_image
|
126
|
+
|
127
|
+
stitched_image = stitch_tiles(zoom, sw_lat, sw_lon, ne_lat, ne_lon)
|
128
|
+
|
129
|
+
if stitched_image:
|
130
|
+
|
131
|
+
stitched_image_path = "osm_map.png"
|
132
|
+
stitched_image.save(stitched_image_path)
|
133
|
+
|
134
|
+
else:
|
135
|
+
print("Error while building stitched images")
|
136
|
+
|
137
|
+
|
138
|
+
def map_with_basemap(
|
139
|
+
layers: list,
|
140
|
+
osm_basemap: bool = True,
|
141
|
+
zoom: int = 15,
|
142
|
+
styles: dict = {},
|
143
|
+
save_output: bool = True,
|
144
|
+
output_filepath: str = "osm_map_with_basemap.png",
|
145
|
+
output_dpi = 300
|
146
|
+
):
|
147
|
+
|
148
|
+
if not layers:
|
149
|
+
raise ValueError("List layers is empty")
|
150
|
+
|
151
|
+
combined = gpd.GeoDataFrame(
|
152
|
+
pd.concat(
|
153
|
+
layers,
|
154
|
+
ignore_index = True
|
155
|
+
),
|
156
|
+
crs = layers[0].crs
|
157
|
+
)
|
158
|
+
|
159
|
+
combined_wgs84 = combined.to_crs(epsg=4326)
|
160
|
+
bounds = combined_wgs84.total_bounds
|
161
|
+
|
162
|
+
sw_lon, sw_lat, ne_lon, ne_lat = bounds[0]*0.9999, bounds[1]*0.9999, bounds[2]*1.0001, bounds[3]*1.0001
|
163
|
+
|
164
|
+
if osm_basemap:
|
165
|
+
|
166
|
+
get_basemap(sw_lat, sw_lon, ne_lat, ne_lon, zoom=zoom)
|
167
|
+
|
168
|
+
fig, ax = plt.subplots(figsize=(10, 10))
|
169
|
+
|
170
|
+
if osm_basemap:
|
171
|
+
|
172
|
+
img = Image.open("osm_map.png")
|
173
|
+
extent_img = [sw_lon, ne_lon, sw_lat, ne_lat]
|
174
|
+
ax.imshow(img, extent=extent_img, origin="upper")
|
175
|
+
|
176
|
+
i = 0
|
177
|
+
for layer in layers:
|
178
|
+
|
179
|
+
layer_3857 = layer.to_crs(epsg=3857)
|
180
|
+
|
181
|
+
if styles != {}:
|
182
|
+
|
183
|
+
layer_style = styles[i]
|
184
|
+
layer_color = layer_style["color"]
|
185
|
+
layer_alpha = layer_style["alpha"]
|
186
|
+
|
187
|
+
if isinstance(layer_color, str):
|
188
|
+
|
189
|
+
layer_3857.plot(
|
190
|
+
ax=ax,
|
191
|
+
color=layer_color,
|
192
|
+
alpha=layer_alpha
|
193
|
+
)
|
194
|
+
|
195
|
+
elif isinstance(layer_color, dict):
|
196
|
+
|
197
|
+
color_key = list(layer_color.keys())[0]
|
198
|
+
color_mapping = layer_color[color_key]
|
199
|
+
|
200
|
+
if color_key not in layer_3857.columns:
|
201
|
+
raise KeyError ("Column", color_key, "not in layer.")
|
202
|
+
|
203
|
+
for value, color in color_mapping.items():
|
204
|
+
|
205
|
+
subset = layer_3857[layer_3857[color_key].astype(str) == str(value)]
|
206
|
+
|
207
|
+
if not subset.empty:
|
208
|
+
subset.plot(
|
209
|
+
ax=ax,
|
210
|
+
color=color,
|
211
|
+
alpha=layer_alpha
|
212
|
+
)
|
213
|
+
|
214
|
+
else:
|
215
|
+
|
216
|
+
layer_3857.plot(
|
217
|
+
ax=ax,
|
218
|
+
alpha=0.6
|
219
|
+
)
|
220
|
+
|
221
|
+
i = i+1
|
222
|
+
|
223
|
+
bbox = box(sw_lon, sw_lat, ne_lon, ne_lat)
|
224
|
+
extent_geom = gpd.GeoSeries([bbox], crs=4326).to_crs(epsg=3857).total_bounds
|
225
|
+
|
226
|
+
ax.set_xlim(extent_geom[0], extent_geom[2])
|
227
|
+
ax.set_ylim(extent_geom[1], extent_geom[3])
|
228
|
+
|
229
|
+
if osm_basemap:
|
230
|
+
cx.add_basemap(
|
231
|
+
ax,
|
232
|
+
source=cx.providers.OpenStreetMap.Mapnik,
|
233
|
+
zoom=zoom
|
234
|
+
)
|
235
|
+
|
236
|
+
plt.axis('off')
|
237
|
+
plt.show()
|
238
|
+
|
239
|
+
if save_output:
|
240
|
+
|
241
|
+
plt.savefig(
|
242
|
+
output_filepath,
|
243
|
+
dpi = output_dpi,
|
244
|
+
bbox_inches="tight"
|
245
|
+
)
|
246
|
+
|
247
|
+
plt.close()
|
248
|
+
|
249
|
+
if os.path.exists("osm_map.png"):
|
250
|
+
os.remove("osm_map.png")
|
huff/tests/data/Haslach.qmd
CHANGED
@@ -1,43 +1,43 @@
|
|
1
|
-
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
|
2
|
-
<qgis version="3.28.3-Firenze">
|
3
|
-
<identifier></identifier>
|
4
|
-
<parentidentifier></parentidentifier>
|
5
|
-
<language></language>
|
6
|
-
<type>dataset</type>
|
7
|
-
<title></title>
|
8
|
-
<abstract></abstract>
|
9
|
-
<contact>
|
10
|
-
<name></name>
|
11
|
-
<organization></organization>
|
12
|
-
<position></position>
|
13
|
-
<voice></voice>
|
14
|
-
<fax></fax>
|
15
|
-
<email></email>
|
16
|
-
<role></role>
|
17
|
-
</contact>
|
18
|
-
<links/>
|
19
|
-
<fees></fees>
|
20
|
-
<encoding></encoding>
|
21
|
-
<crs>
|
22
|
-
<spatialrefsys nativeFormat="Wkt">
|
23
|
-
<wkt>PROJCRS["DHDN / 3-degree Gauss-Kruger zone 3",BASEGEOGCRS["DHDN",DATUM["Deutsches Hauptdreiecksnetz",ELLIPSOID["Bessel 1841",6377397.155,299.1528128,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4314]],CONVERSION["3-degree Gauss-Kruger zone 3",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",1,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",3500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["northing (X)",north,ORDER[1],LENGTHUNIT["metre",1]],AXIS["easting (Y)",east,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Cadastre, engineering survey, topographic mapping."],AREA["Germany - former West Germany onshore between 7�30'E and 10�30'E - states of Baden-Wurtemberg, Bayern, Bremen, Hamberg, Hessen, Niedersachsen, Nordrhein-Westfalen, Rhineland-Pfalz, Schleswig-Holstein."],BBOX[47.27,7.5,55.09,10.51]],ID["EPSG",31467]]</wkt>
|
24
|
-
<proj4>+proj=tmerc +lat_0=0 +lon_0=9 +k=1 +x_0=3500000 +y_0=0 +ellps=bessel +towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7 +units=m +no_defs</proj4>
|
25
|
-
<srsid>2647</srsid>
|
26
|
-
<srid>31467</srid>
|
27
|
-
<authid>EPSG:31467</authid>
|
28
|
-
<description>DHDN / 3-degree Gauss-Kruger zone 3</description>
|
29
|
-
<projectionacronym>tmerc</projectionacronym>
|
30
|
-
<ellipsoidacronym>EPSG:7004</ellipsoidacronym>
|
31
|
-
<geographicflag>false</geographicflag>
|
32
|
-
</spatialrefsys>
|
33
|
-
</crs>
|
34
|
-
<extent>
|
35
|
-
<spatial maxy="0" dimensions="2" maxx="0" maxz="0" crs="EPSG:31467" miny="0" minz="0" minx="0"/>
|
36
|
-
<temporal>
|
37
|
-
<period>
|
38
|
-
<start></start>
|
39
|
-
<end></end>
|
40
|
-
</period>
|
41
|
-
</temporal>
|
42
|
-
</extent>
|
43
|
-
</qgis>
|
1
|
+
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
|
2
|
+
<qgis version="3.28.3-Firenze">
|
3
|
+
<identifier></identifier>
|
4
|
+
<parentidentifier></parentidentifier>
|
5
|
+
<language></language>
|
6
|
+
<type>dataset</type>
|
7
|
+
<title></title>
|
8
|
+
<abstract></abstract>
|
9
|
+
<contact>
|
10
|
+
<name></name>
|
11
|
+
<organization></organization>
|
12
|
+
<position></position>
|
13
|
+
<voice></voice>
|
14
|
+
<fax></fax>
|
15
|
+
<email></email>
|
16
|
+
<role></role>
|
17
|
+
</contact>
|
18
|
+
<links/>
|
19
|
+
<fees></fees>
|
20
|
+
<encoding></encoding>
|
21
|
+
<crs>
|
22
|
+
<spatialrefsys nativeFormat="Wkt">
|
23
|
+
<wkt>PROJCRS["DHDN / 3-degree Gauss-Kruger zone 3",BASEGEOGCRS["DHDN",DATUM["Deutsches Hauptdreiecksnetz",ELLIPSOID["Bessel 1841",6377397.155,299.1528128,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4314]],CONVERSION["3-degree Gauss-Kruger zone 3",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",1,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",3500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["northing (X)",north,ORDER[1],LENGTHUNIT["metre",1]],AXIS["easting (Y)",east,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Cadastre, engineering survey, topographic mapping."],AREA["Germany - former West Germany onshore between 7�30'E and 10�30'E - states of Baden-Wurtemberg, Bayern, Bremen, Hamberg, Hessen, Niedersachsen, Nordrhein-Westfalen, Rhineland-Pfalz, Schleswig-Holstein."],BBOX[47.27,7.5,55.09,10.51]],ID["EPSG",31467]]</wkt>
|
24
|
+
<proj4>+proj=tmerc +lat_0=0 +lon_0=9 +k=1 +x_0=3500000 +y_0=0 +ellps=bessel +towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7 +units=m +no_defs</proj4>
|
25
|
+
<srsid>2647</srsid>
|
26
|
+
<srid>31467</srid>
|
27
|
+
<authid>EPSG:31467</authid>
|
28
|
+
<description>DHDN / 3-degree Gauss-Kruger zone 3</description>
|
29
|
+
<projectionacronym>tmerc</projectionacronym>
|
30
|
+
<ellipsoidacronym>EPSG:7004</ellipsoidacronym>
|
31
|
+
<geographicflag>false</geographicflag>
|
32
|
+
</spatialrefsys>
|
33
|
+
</crs>
|
34
|
+
<extent>
|
35
|
+
<spatial maxy="0" dimensions="2" maxx="0" maxz="0" crs="EPSG:31467" miny="0" minz="0" minx="0"/>
|
36
|
+
<temporal>
|
37
|
+
<period>
|
38
|
+
<start></start>
|
39
|
+
<end></end>
|
40
|
+
</period>
|
41
|
+
</temporal>
|
42
|
+
</extent>
|
43
|
+
</qgis>
|
@@ -1,43 +1,43 @@
|
|
1
|
-
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
|
2
|
-
<qgis version="3.28.3-Firenze">
|
3
|
-
<identifier></identifier>
|
4
|
-
<parentidentifier></parentidentifier>
|
5
|
-
<language></language>
|
6
|
-
<type>dataset</type>
|
7
|
-
<title></title>
|
8
|
-
<abstract></abstract>
|
9
|
-
<contact>
|
10
|
-
<name></name>
|
11
|
-
<organization></organization>
|
12
|
-
<position></position>
|
13
|
-
<voice></voice>
|
14
|
-
<fax></fax>
|
15
|
-
<email></email>
|
16
|
-
<role></role>
|
17
|
-
</contact>
|
18
|
-
<links/>
|
19
|
-
<fees></fees>
|
20
|
-
<encoding></encoding>
|
21
|
-
<crs>
|
22
|
-
<spatialrefsys nativeFormat="Wkt">
|
23
|
-
<wkt>PROJCRS["DHDN / 3-degree Gauss-Kruger zone 3",BASEGEOGCRS["DHDN",DATUM["Deutsches Hauptdreiecksnetz",ELLIPSOID["Bessel 1841",6377397.155,299.1528128,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4314]],CONVERSION["3-degree Gauss-Kruger zone 3",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",1,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",3500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["northing (X)",north,ORDER[1],LENGTHUNIT["metre",1]],AXIS["easting (Y)",east,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Cadastre, engineering survey, topographic mapping."],AREA["Germany - former West Germany onshore between 7�30'E and 10�30'E - states of Baden-Wurtemberg, Bayern, Bremen, Hamberg, Hessen, Niedersachsen, Nordrhein-Westfalen, Rhineland-Pfalz, Schleswig-Holstein."],BBOX[47.27,7.5,55.09,10.51]],ID["EPSG",31467]]</wkt>
|
24
|
-
<proj4>+proj=tmerc +lat_0=0 +lon_0=9 +k=1 +x_0=3500000 +y_0=0 +ellps=bessel +towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7 +units=m +no_defs</proj4>
|
25
|
-
<srsid>2647</srsid>
|
26
|
-
<srid>31467</srid>
|
27
|
-
<authid>EPSG:31467</authid>
|
28
|
-
<description>DHDN / 3-degree Gauss-Kruger zone 3</description>
|
29
|
-
<projectionacronym>tmerc</projectionacronym>
|
30
|
-
<ellipsoidacronym>EPSG:7004</ellipsoidacronym>
|
31
|
-
<geographicflag>false</geographicflag>
|
32
|
-
</spatialrefsys>
|
33
|
-
</crs>
|
34
|
-
<extent>
|
35
|
-
<spatial maxy="0" dimensions="2" maxx="0" maxz="0" crs="EPSG:31467" miny="0" minz="0" minx="0"/>
|
36
|
-
<temporal>
|
37
|
-
<period>
|
38
|
-
<start></start>
|
39
|
-
<end></end>
|
40
|
-
</period>
|
41
|
-
</temporal>
|
42
|
-
</extent>
|
43
|
-
</qgis>
|
1
|
+
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
|
2
|
+
<qgis version="3.28.3-Firenze">
|
3
|
+
<identifier></identifier>
|
4
|
+
<parentidentifier></parentidentifier>
|
5
|
+
<language></language>
|
6
|
+
<type>dataset</type>
|
7
|
+
<title></title>
|
8
|
+
<abstract></abstract>
|
9
|
+
<contact>
|
10
|
+
<name></name>
|
11
|
+
<organization></organization>
|
12
|
+
<position></position>
|
13
|
+
<voice></voice>
|
14
|
+
<fax></fax>
|
15
|
+
<email></email>
|
16
|
+
<role></role>
|
17
|
+
</contact>
|
18
|
+
<links/>
|
19
|
+
<fees></fees>
|
20
|
+
<encoding></encoding>
|
21
|
+
<crs>
|
22
|
+
<spatialrefsys nativeFormat="Wkt">
|
23
|
+
<wkt>PROJCRS["DHDN / 3-degree Gauss-Kruger zone 3",BASEGEOGCRS["DHDN",DATUM["Deutsches Hauptdreiecksnetz",ELLIPSOID["Bessel 1841",6377397.155,299.1528128,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4314]],CONVERSION["3-degree Gauss-Kruger zone 3",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",1,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",3500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["northing (X)",north,ORDER[1],LENGTHUNIT["metre",1]],AXIS["easting (Y)",east,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Cadastre, engineering survey, topographic mapping."],AREA["Germany - former West Germany onshore between 7�30'E and 10�30'E - states of Baden-Wurtemberg, Bayern, Bremen, Hamberg, Hessen, Niedersachsen, Nordrhein-Westfalen, Rhineland-Pfalz, Schleswig-Holstein."],BBOX[47.27,7.5,55.09,10.51]],ID["EPSG",31467]]</wkt>
|
24
|
+
<proj4>+proj=tmerc +lat_0=0 +lon_0=9 +k=1 +x_0=3500000 +y_0=0 +ellps=bessel +towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7 +units=m +no_defs</proj4>
|
25
|
+
<srsid>2647</srsid>
|
26
|
+
<srid>31467</srid>
|
27
|
+
<authid>EPSG:31467</authid>
|
28
|
+
<description>DHDN / 3-degree Gauss-Kruger zone 3</description>
|
29
|
+
<projectionacronym>tmerc</projectionacronym>
|
30
|
+
<ellipsoidacronym>EPSG:7004</ellipsoidacronym>
|
31
|
+
<geographicflag>false</geographicflag>
|
32
|
+
</spatialrefsys>
|
33
|
+
</crs>
|
34
|
+
<extent>
|
35
|
+
<spatial maxy="0" dimensions="2" maxx="0" maxz="0" crs="EPSG:31467" miny="0" minz="0" minx="0"/>
|
36
|
+
<temporal>
|
37
|
+
<period>
|
38
|
+
<start></start>
|
39
|
+
<end></end>
|
40
|
+
</period>
|
41
|
+
</temporal>
|
42
|
+
</extent>
|
43
|
+
</qgis>
|
huff/tests/tests_huff.py
CHANGED
@@ -4,13 +4,16 @@
|
|
4
4
|
# Author: Thomas Wieland
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
6
6
|
# mail: geowieland@googlemail.com
|
7
|
-
# Version: 1.
|
8
|
-
# Last update: 2025-05-
|
7
|
+
# Version: 1.3.1
|
8
|
+
# Last update: 2025-05-28 13:10
|
9
9
|
# Copyright (c) 2025 Thomas Wieland
|
10
10
|
#-----------------------------------------------------------------------
|
11
11
|
|
12
12
|
|
13
13
|
from huff.models import create_interaction_matrix, get_isochrones, load_geodata, load_interaction_matrix
|
14
|
+
from huff.osm import map_with_basemap
|
15
|
+
from huff.gistools import buffers, point_spatial_join
|
16
|
+
|
14
17
|
|
15
18
|
# Customer origins (statistical districts):
|
16
19
|
|
@@ -21,6 +24,14 @@ Haslach = load_geodata(
|
|
21
24
|
)
|
22
25
|
# Loading customer origins (shapefile)
|
23
26
|
|
27
|
+
Haslach_buf = Haslach.buffers(
|
28
|
+
segments_distance=[500,1000,1500],
|
29
|
+
save_output=True,
|
30
|
+
output_filepath="Haslach_buf.shp",
|
31
|
+
output_crs="EPSG:31467"
|
32
|
+
)
|
33
|
+
# Buffers for customer origins
|
34
|
+
|
24
35
|
Haslach.summary()
|
25
36
|
# Summary of customer origins
|
26
37
|
|
@@ -56,22 +67,24 @@ Haslach_supermarkets.define_attraction_weighting(
|
|
56
67
|
)
|
57
68
|
# Define attraction weighting (gamma)
|
58
69
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
70
|
+
Haslach_supermarkets.isochrones(
|
71
|
+
segments_minutes=[3, 6, 9, 12, 15],
|
72
|
+
profile = "foot-walking",
|
73
|
+
save_output=True,
|
74
|
+
ors_auth="5b3ce3597851110001cf62480a15aafdb5a64f4d91805929f8af6abd",
|
75
|
+
output_filepath="Haslach_supermarkets_iso.shp",
|
76
|
+
output_crs="EPSG:31467"
|
77
|
+
)
|
78
|
+
# Obtaining isochrones for walking (5 and 10 minutes)
|
79
|
+
# ORS API documentation: https://openrouteservice.org/dev/#/api-docs/v2/
|
67
80
|
|
68
|
-
|
69
|
-
#
|
81
|
+
Haslach_supermarkets.summary()
|
82
|
+
# Summary of updated customer origins
|
70
83
|
|
71
|
-
|
72
|
-
#
|
84
|
+
Haslach_supermarkets_isochrones = Haslach_supermarkets.get_isochrones_gdf()
|
85
|
+
# Extracting isochrones
|
73
86
|
|
74
|
-
|
87
|
+
print(Haslach_supermarkets_isochrones)
|
75
88
|
|
76
89
|
|
77
90
|
# Using customer origins and supply locations for building interaction matrix:
|
@@ -83,10 +96,11 @@ haslach_interactionmatrix = create_interaction_matrix(
|
|
83
96
|
# Creating interaction matrix
|
84
97
|
|
85
98
|
interaction_matrix = haslach_interactionmatrix.transport_costs(
|
86
|
-
|
87
|
-
network=False
|
99
|
+
ors_auth="5b3ce3597851110001cf62480a15aafdb5a64f4d91805929f8af6abd"
|
100
|
+
#network=False
|
88
101
|
)
|
89
102
|
# Obtaining transport costs (default: driving-car)
|
103
|
+
# ORS API documentation: https://openrouteservice.org/dev/#/api-docs/v2/
|
90
104
|
|
91
105
|
interaction_matrix = interaction_matrix.flows()
|
92
106
|
# Calculating spatial flows
|
@@ -120,6 +134,7 @@ mci_fit.get_market_areas_df()
|
|
120
134
|
|
121
135
|
|
122
136
|
# Loading own interaction matrix:
|
137
|
+
# Data source: Wieland 2015 (https://nbn-resolving.org/urn:nbn:de:bvb:20-opus-180753)
|
123
138
|
|
124
139
|
Wieland2015_interaction_matrix = load_interaction_matrix(
|
125
140
|
data="data/Wieland2015.xlsx",
|
@@ -134,7 +149,6 @@ Wieland2015_interaction_matrix = load_interaction_matrix(
|
|
134
149
|
probabilities_col="MA",
|
135
150
|
data_type="xlsx"
|
136
151
|
)
|
137
|
-
# Data source: Wieland 2015 (https://nbn-resolving.org/urn:nbn:de:bvb:20-opus-180753)
|
138
152
|
|
139
153
|
Wieland2015_interaction_matrix.summary()
|
140
154
|
# Summary of interaction matrix
|
@@ -150,4 +164,97 @@ Wieland2015_fit = Wieland2015_interaction_matrix.mci_fit(
|
|
150
164
|
# Fitting MCI model with four independent variables
|
151
165
|
|
152
166
|
Wieland2015_fit.summary()
|
153
|
-
# MCI model summary
|
167
|
+
# MCI model summary
|
168
|
+
|
169
|
+
|
170
|
+
# Buffer analysis:
|
171
|
+
|
172
|
+
Haslach_supermarkets_gdf = Haslach_supermarkets.get_geodata_gpd_original()
|
173
|
+
Haslach_buffers = Haslach_buf.get_buffers_gdf()
|
174
|
+
# Extracting points and buffer polygons
|
175
|
+
|
176
|
+
Haslach_districts_buf = point_spatial_join(
|
177
|
+
polygon_gdf = Haslach_buffers,
|
178
|
+
point_gdf = Haslach_supermarkets_gdf,
|
179
|
+
polygon_ref_cols = ["BEZEICHN", "segment"],
|
180
|
+
point_stat_col = "VKF_qm"
|
181
|
+
)
|
182
|
+
# Spatial join with buffers and points
|
183
|
+
# Statistics for supermarket selling space by buffers of statistical districts
|
184
|
+
# (How much selling space in 500, 1000, and 1500 metres?)
|
185
|
+
|
186
|
+
Haslach_districts_buf[0].to_file("Haslach_districts_buf.shp")
|
187
|
+
# Save joined points as shapefile
|
188
|
+
|
189
|
+
print(Haslach_districts_buf[1])
|
190
|
+
# Showing df with overlay statistics
|
191
|
+
|
192
|
+
|
193
|
+
# Isochrones analysis:
|
194
|
+
|
195
|
+
Haslach_districts = Haslach.get_geodata_gpd_original()
|
196
|
+
|
197
|
+
Haslach_supermarkets_iso = point_spatial_join(
|
198
|
+
polygon_gdf = Haslach_supermarkets_isochrones,
|
199
|
+
point_gdf = Haslach_districts,
|
200
|
+
polygon_ref_cols = ["LFDNR", "segment"],
|
201
|
+
point_stat_col = "pop"
|
202
|
+
)
|
203
|
+
# Spatial join with isochrones and points
|
204
|
+
# Statistics for population by isochrones of supermarkets
|
205
|
+
# (How much population in 5, 10, and 15 minutes?)
|
206
|
+
|
207
|
+
Haslach_supermarkets_iso[0].to_file("Haslach_supermarkets_iso.shp")
|
208
|
+
# Save joined points as shapefile
|
209
|
+
|
210
|
+
print(Haslach_supermarkets_iso[1])
|
211
|
+
# Showing df with overlay statistics
|
212
|
+
|
213
|
+
|
214
|
+
# Creating map:
|
215
|
+
|
216
|
+
Haslach_gdf = Haslach.get_geodata_gpd_original()
|
217
|
+
Haslach_supermarkets_gdf = Haslach_supermarkets.get_geodata_gpd_original()
|
218
|
+
Haslach_supermarkets_gdf_iso = Haslach_supermarkets.get_isochrones_gdf()
|
219
|
+
# Extracttion geopandas.GeoDataFrames
|
220
|
+
|
221
|
+
map_with_basemap(
|
222
|
+
layers = [
|
223
|
+
Haslach_supermarkets_gdf_iso,
|
224
|
+
Haslach_gdf,
|
225
|
+
Haslach_supermarkets_gdf
|
226
|
+
],
|
227
|
+
styles={
|
228
|
+
0: {
|
229
|
+
"color": {
|
230
|
+
"segment_minutes": {
|
231
|
+
"3": "green",
|
232
|
+
"6": "yellow",
|
233
|
+
"9": "orange",
|
234
|
+
"12": "red",
|
235
|
+
"13": "darkred"
|
236
|
+
}
|
237
|
+
},
|
238
|
+
"alpha": 0.3
|
239
|
+
},
|
240
|
+
1: {
|
241
|
+
"color": "green",
|
242
|
+
"alpha": 1
|
243
|
+
},
|
244
|
+
2: {
|
245
|
+
"color": {
|
246
|
+
"Name": {
|
247
|
+
"Aldi S├╝d": "blue",
|
248
|
+
"Edeka": "yellow",
|
249
|
+
"Lidl": "red",
|
250
|
+
"Netto": "orange",
|
251
|
+
"Real": "black",
|
252
|
+
"Treff 3000": "fuchsia"
|
253
|
+
}
|
254
|
+
},
|
255
|
+
"alpha": 1
|
256
|
+
}
|
257
|
+
},
|
258
|
+
output_filepath = "Haslach_map.png"
|
259
|
+
)
|
260
|
+
# Map with three layers and OSM basemap
|