huff 1.5.6__py3-none-any.whl → 1.5.8__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 +72 -8
- huff/models.py +28 -2
- huff/osm.py +27 -19
- huff/tests/tests_huff.py +2 -2
- {huff-1.5.6.dist-info → huff-1.5.8.dist-info}/METADATA +17 -15
- {huff-1.5.6.dist-info → huff-1.5.8.dist-info}/RECORD +8 -8
- {huff-1.5.6.dist-info → huff-1.5.8.dist-info}/WHEEL +0 -0
- {huff-1.5.6.dist-info → huff-1.5.8.dist-info}/top_level.txt +0 -0
huff/gistools.py
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
# Author: Thomas Wieland
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
6
6
|
# mail: geowieland@googlemail.com
|
7
|
-
# Version: 1.4.
|
8
|
-
# Last update: 2025-07
|
7
|
+
# Version: 1.4.3
|
8
|
+
# Last update: 2025-08-07 17:20
|
9
9
|
# Copyright (c) 2025 Thomas Wieland
|
10
10
|
#-----------------------------------------------------------------------
|
11
11
|
|
@@ -83,7 +83,7 @@ def distance_matrix(
|
|
83
83
|
matrix.append(row)
|
84
84
|
|
85
85
|
if lines_gdf:
|
86
|
-
return line_data
|
86
|
+
return gp.GeoDataFrame(line_data)
|
87
87
|
else:
|
88
88
|
return matrix
|
89
89
|
|
@@ -95,9 +95,15 @@ def buffers(
|
|
95
95
|
donut: bool = True,
|
96
96
|
save_output: bool = True,
|
97
97
|
output_filepath: str = "buffers.shp",
|
98
|
-
output_crs: str = "EPSG:4326"
|
98
|
+
output_crs: str = "EPSG:4326"
|
99
99
|
):
|
100
|
+
|
101
|
+
if point_gdf.crs.is_geographic:
|
102
|
+
print(f"WARNING: Point GeoDataFrame has geographic coordinate system {point_gdf.crs}. Results may be invalid.")
|
100
103
|
|
104
|
+
if unique_id_col not in point_gdf.columns:
|
105
|
+
raise KeyError(f"No column {unique_id_col} in input GeoDataFrame")
|
106
|
+
|
101
107
|
all_buffers_gdf = gp.GeoDataFrame(
|
102
108
|
columns=[
|
103
109
|
unique_id_col,
|
@@ -146,15 +152,72 @@ def buffers(
|
|
146
152
|
|
147
153
|
all_buffers_gdf = all_buffers_gdf.to_crs(output_crs)
|
148
154
|
|
149
|
-
if save_output:
|
150
|
-
|
151
|
-
all_buffers_gdf.to_file(output_filepath)
|
152
|
-
|
155
|
+
if save_output:
|
156
|
+
all_buffers_gdf.to_file(output_filepath)
|
153
157
|
print ("Saved as", output_filepath)
|
154
158
|
|
155
159
|
return all_buffers_gdf
|
156
160
|
|
157
161
|
|
162
|
+
def polygon_select(
|
163
|
+
gdf: gp.GeoDataFrame,
|
164
|
+
gdf_unique_id_col: str,
|
165
|
+
gdf_polygon_select: gp.GeoDataFrame,
|
166
|
+
gdf_polygon_select_unique_id_col: str,
|
167
|
+
distance: int,
|
168
|
+
within: bool = False,
|
169
|
+
save_output: bool = True,
|
170
|
+
output_filepath: str = "polygon_select.shp",
|
171
|
+
output_crs: str = "EPSG:4326"
|
172
|
+
):
|
173
|
+
|
174
|
+
if gdf.crs != gdf_polygon_select.crs:
|
175
|
+
raise ValueError(f"Coordinate reference systems of inputs do not match. Polygons: {str(gdf.crs)}, points: {str(gdf_polygon_select.crs)}")
|
176
|
+
|
177
|
+
if gdf_unique_id_col not in gdf.columns:
|
178
|
+
raise KeyError(f"No column {gdf_unique_id_col} in input GeoDataFrame")
|
179
|
+
|
180
|
+
if gdf_polygon_select_unique_id_col not in gdf_polygon_select.columns:
|
181
|
+
raise KeyError(f"No column {gdf_polygon_select_unique_id_col} in input GeoDataFrame for selection")
|
182
|
+
|
183
|
+
if gdf.crs.is_geographic:
|
184
|
+
print(f"WARNING: Input GeoDataFrames have geographic coordinate system {gdf.crs}. Results may be invalid.")
|
185
|
+
|
186
|
+
if len(gdf) > 1:
|
187
|
+
print(f"WARNING: Input GeoDataFrame 'gdf' includes > 1 objects. Using the first only.")
|
188
|
+
gdf = gdf[0]
|
189
|
+
|
190
|
+
gdf_buffer = buffers(
|
191
|
+
point_gdf = gdf,
|
192
|
+
unique_id_col = gdf_unique_id_col,
|
193
|
+
distances = [distance],
|
194
|
+
save_output = True,
|
195
|
+
output_filepath = "gdf_buffer.shp",
|
196
|
+
output_crs = output_crs
|
197
|
+
)
|
198
|
+
|
199
|
+
gdf_buffer = gdf_buffer.geometry.union_all()
|
200
|
+
|
201
|
+
gdf_polygon_select = gdf_polygon_select.to_crs(output_crs)
|
202
|
+
|
203
|
+
gdf_select_intersects = gdf_polygon_select[
|
204
|
+
gdf_polygon_select.geometry.intersects(gdf_buffer)
|
205
|
+
]
|
206
|
+
|
207
|
+
if within:
|
208
|
+
gdf_select_intersects = gdf_select_intersects[gdf_select_intersects.geometry.within(gdf_buffer)]
|
209
|
+
|
210
|
+
gdf_select_intersects_unique_ids = gdf_select_intersects[gdf_polygon_select_unique_id_col].unique()
|
211
|
+
|
212
|
+
gdf_polygon_select_selection = gdf_polygon_select[gdf_polygon_select[gdf_polygon_select_unique_id_col].isin(gdf_select_intersects_unique_ids)]
|
213
|
+
|
214
|
+
if save_output:
|
215
|
+
gdf_polygon_select_selection.to_file(output_filepath)
|
216
|
+
print ("Saved as", output_filepath)
|
217
|
+
|
218
|
+
return gdf_polygon_select_selection
|
219
|
+
|
220
|
+
|
158
221
|
def overlay_difference(
|
159
222
|
polygon_gdf: gp.GeoDataFrame,
|
160
223
|
sort_col: str = None,
|
@@ -254,6 +317,7 @@ def point_spatial_join(
|
|
254
317
|
spatial_join_stat
|
255
318
|
]
|
256
319
|
|
320
|
+
|
257
321
|
def map_with_basemap(
|
258
322
|
layers: list,
|
259
323
|
osm_basemap: bool = True,
|
huff/models.py
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
# Author: Thomas Wieland
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
6
6
|
# mail: geowieland@googlemail.com
|
7
|
-
# Version: 1.5.
|
8
|
-
# Last update: 2025-
|
7
|
+
# Version: 1.5.6
|
8
|
+
# Last update: 2025-08-01 14:00
|
9
9
|
# Copyright (c) 2025 Thomas Wieland
|
10
10
|
#-----------------------------------------------------------------------
|
11
11
|
|
@@ -590,6 +590,12 @@ class InteractionMatrix:
|
|
590
590
|
if interaction_matrix_metadata["fit"]["function"] == "huff_ml_fit":
|
591
591
|
print("Fit method " + interaction_matrix_metadata["fit"]["method"] + " (Converged: " + str(interaction_matrix_metadata["fit"]["minimize_success"]) + ")")
|
592
592
|
|
593
|
+
return [
|
594
|
+
customer_origins_metadata,
|
595
|
+
supply_locations_metadata,
|
596
|
+
interaction_matrix_metadata
|
597
|
+
]
|
598
|
+
|
593
599
|
def transport_costs(
|
594
600
|
self,
|
595
601
|
network: bool = True,
|
@@ -1642,6 +1648,8 @@ class HuffModel:
|
|
1642
1648
|
|
1643
1649
|
print("----------------------------------")
|
1644
1650
|
|
1651
|
+
huff_modelfit = None
|
1652
|
+
|
1645
1653
|
if interaction_matrix_metadata != {} and "fit" in interaction_matrix_metadata and interaction_matrix_metadata["fit"]["function"] is not None:
|
1646
1654
|
print("Parameter estimation")
|
1647
1655
|
print("Fit function " + interaction_matrix_metadata["fit"]["function"])
|
@@ -1682,6 +1690,13 @@ class HuffModel:
|
|
1682
1690
|
|
1683
1691
|
print("----------------------------------")
|
1684
1692
|
|
1693
|
+
return [
|
1694
|
+
customer_origins_metadata,
|
1695
|
+
supply_locations_metadata,
|
1696
|
+
interaction_matrix_metadata,
|
1697
|
+
huff_modelfit
|
1698
|
+
]
|
1699
|
+
|
1685
1700
|
def mci_fit(
|
1686
1701
|
self,
|
1687
1702
|
cols: list = ["A_j", "t_ij"],
|
@@ -2341,6 +2356,7 @@ class MCIModel:
|
|
2341
2356
|
|
2342
2357
|
customer_origins_metadata = interaction_matrix.get_customer_origins().get_metadata()
|
2343
2358
|
supply_locations_metadata = interaction_matrix.get_supply_locations().get_metadata()
|
2359
|
+
interaction_matrix_metadata = interaction_matrix.get_metadata()
|
2344
2360
|
|
2345
2361
|
print("Multiplicative Competitive Interaction Model")
|
2346
2362
|
print("--------------------------------------------")
|
@@ -2371,7 +2387,10 @@ class MCIModel:
|
|
2371
2387
|
|
2372
2388
|
print("--------------------------------------------")
|
2373
2389
|
|
2390
|
+
mci_modelfit = None
|
2391
|
+
|
2374
2392
|
mci_modelfit = self.modelfit()
|
2393
|
+
|
2375
2394
|
if mci_modelfit is not None:
|
2376
2395
|
|
2377
2396
|
print ("Goodness-of-fit for probabilities")
|
@@ -2402,6 +2421,13 @@ class MCIModel:
|
|
2402
2421
|
print(APE_df.to_string(index=False))
|
2403
2422
|
|
2404
2423
|
print("--------------------------------------------")
|
2424
|
+
|
2425
|
+
return [
|
2426
|
+
customer_origins_metadata,
|
2427
|
+
supply_locations_metadata,
|
2428
|
+
interaction_matrix_metadata,
|
2429
|
+
mci_modelfit
|
2430
|
+
]
|
2405
2431
|
|
2406
2432
|
def utility(
|
2407
2433
|
self,
|
huff/osm.py
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
# Author: Thomas Wieland
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
6
6
|
# mail: geowieland@googlemail.com
|
7
|
-
# Version: 1.4.
|
8
|
-
# Last update: 2025-07
|
7
|
+
# Version: 1.4.3
|
8
|
+
# Last update: 2025-08-07 17:21
|
9
9
|
# Copyright (c) 2025 Thomas Wieland
|
10
10
|
#-----------------------------------------------------------------------
|
11
11
|
|
@@ -23,7 +23,7 @@ class Client:
|
|
23
23
|
self,
|
24
24
|
server = "http://a.tile.openstreetmap.org/",
|
25
25
|
headers = {
|
26
|
-
'User-Agent': 'huff.osm/1.
|
26
|
+
'User-Agent': 'huff.osm/1.4.3 (your_name@your_email_provider.com)'
|
27
27
|
}
|
28
28
|
):
|
29
29
|
|
@@ -40,23 +40,31 @@ class Client:
|
|
40
40
|
|
41
41
|
osm_url = self.server + f"{zoom}/{x}/{y}.png"
|
42
42
|
|
43
|
-
|
44
|
-
osm_url,
|
45
|
-
headers = self.headers,
|
46
|
-
timeout = timeout
|
47
|
-
)
|
48
|
-
|
49
|
-
if response.status_code == 200:
|
50
|
-
|
51
|
-
with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp_file:
|
52
|
-
tmp_file.write(response.content)
|
53
|
-
tmp_file_path = tmp_file.name
|
54
|
-
return Image.open(tmp_file_path)
|
43
|
+
try:
|
55
44
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
45
|
+
response = requests.get(
|
46
|
+
osm_url,
|
47
|
+
headers = self.headers,
|
48
|
+
timeout = timeout
|
49
|
+
)
|
50
|
+
|
51
|
+
if response.status_code == 200:
|
52
|
+
|
53
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp_file:
|
54
|
+
tmp_file.write(response.content)
|
55
|
+
tmp_file_path = tmp_file.name
|
56
|
+
return Image.open(tmp_file_path)
|
57
|
+
|
58
|
+
else:
|
59
|
+
|
60
|
+
print(f"Error while accessing OSM server with URL {osm_url}. Status code: {response.status_code} - {response.reason}")
|
61
|
+
|
62
|
+
return None
|
63
|
+
|
64
|
+
except Exception as e:
|
65
|
+
|
66
|
+
print(f"Error while accessing OSM server with URL {osm_url}. Error message: {e}")
|
67
|
+
|
60
68
|
return None
|
61
69
|
|
62
70
|
|
huff/tests/tests_huff.py
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
# Author: Thomas Wieland
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
6
6
|
# mail: geowieland@googlemail.com
|
7
|
-
# Version: 1.5.
|
8
|
-
# Last update: 2025-07
|
7
|
+
# Version: 1.5.8
|
8
|
+
# Last update: 2025-08-07 17:20
|
9
9
|
# Copyright (c) 2025 Thomas Wieland
|
10
10
|
#-----------------------------------------------------------------------
|
11
11
|
|
@@ -1,20 +1,20 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: huff
|
3
|
-
Version: 1.5.
|
3
|
+
Version: 1.5.8
|
4
4
|
Summary: huff: Huff Model Market Area Analysis
|
5
5
|
Author: Thomas Wieland
|
6
6
|
Author-email: geowieland@googlemail.com
|
7
7
|
Description-Content-Type: text/markdown
|
8
|
-
Requires-Dist: geopandas
|
9
|
-
Requires-Dist: pandas
|
10
|
-
Requires-Dist: numpy
|
11
|
-
Requires-Dist: statsmodels
|
12
|
-
Requires-Dist: shapely
|
13
|
-
Requires-Dist: requests
|
14
|
-
Requires-Dist: matplotlib
|
15
|
-
Requires-Dist: pillow
|
16
|
-
Requires-Dist: contextily
|
17
|
-
Requires-Dist: openpyxl
|
8
|
+
Requires-Dist: geopandas==0.14.4
|
9
|
+
Requires-Dist: pandas==2.2.3
|
10
|
+
Requires-Dist: numpy==1.26.3
|
11
|
+
Requires-Dist: statsmodels==0.14.1
|
12
|
+
Requires-Dist: shapely==2.0.4
|
13
|
+
Requires-Dist: requests==2.31.0
|
14
|
+
Requires-Dist: matplotlib==3.8.2
|
15
|
+
Requires-Dist: pillow==10.2.0
|
16
|
+
Requires-Dist: contextily==1.6.2
|
17
|
+
Requires-Dist: openpyxl==3.1.4
|
18
18
|
|
19
19
|
# huff: Huff Model Market Area Analysis
|
20
20
|
|
@@ -28,11 +28,13 @@ Thomas Wieland [ORCID](https://orcid.org/0000-0001-5168-9846) [EMail](mailto:geo
|
|
28
28
|
See the /tests directory for usage examples of most of the included functions.
|
29
29
|
|
30
30
|
|
31
|
-
## Updates v1.5.
|
31
|
+
## Updates v1.5.8
|
32
|
+
- Bugfixes:
|
33
|
+
- buffers() now checks whether unique_id_col exists
|
34
|
+
- buffers() now checks whether input gdf has geographic coordinate system
|
35
|
+
- download_tile() which is used in map_with_basemap() now controls for timeouts
|
32
36
|
- Extensions:
|
33
|
-
-
|
34
|
-
- Other
|
35
|
-
- map_with_basemap() now belongs to huff.gistools
|
37
|
+
- Function polygon_select(): Selection of polygons from point's airline distance
|
36
38
|
|
37
39
|
|
38
40
|
## Features
|
@@ -1,10 +1,10 @@
|
|
1
1
|
huff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
huff/gistools.py,sha256=
|
3
|
-
huff/models.py,sha256=
|
2
|
+
huff/gistools.py,sha256=L7fw9AE749kgzhFW8kpiPXHF8WWQIpD1dKDX2zSicO0,15511
|
3
|
+
huff/models.py,sha256=KBey1HLPKDHLfT6WkDofeF3XGhp8c19FfAyhietvM8U,135564
|
4
4
|
huff/ors.py,sha256=JlO2UEishQX87PIiktksOrVT5QdB-GEWgjXcxoR_KuA,11929
|
5
|
-
huff/osm.py,sha256=
|
5
|
+
huff/osm.py,sha256=vQMBocUt8r_2UhVAPzA9csOTrOuVHonTPk12L-4eBG4,3856
|
6
6
|
huff/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
-
huff/tests/tests_huff.py,sha256=
|
7
|
+
huff/tests/tests_huff.py,sha256=lbbmxJEZZ8kTnW9qwLmNTOOKgHOVOXsBCrSuKKz0umw,13095
|
8
8
|
huff/tests/data/Haslach.cpg,sha256=OtMDH1UDpEBK-CUmLugjLMBNTqZoPULF3QovKiesmCQ,5
|
9
9
|
huff/tests/data/Haslach.dbf,sha256=GVPIt05OzDO7UrRDcsMhiYWvyXAPg6Z-qkiysFzj-fc,506
|
10
10
|
huff/tests/data/Haslach.prj,sha256=2Jy1Vlzh7UxQ1MXpZ9UYLs2SxfrObj2xkEkZyLqmGTY,437
|
@@ -24,7 +24,7 @@ huff/tests/data/Haslach_supermarkets.qmd,sha256=JlcOYzG4vI1NH1IuOpxwIPnJsCyC-pDR
|
|
24
24
|
huff/tests/data/Haslach_supermarkets.shp,sha256=X7QbQ0BTMag_B-bDRbpr-go2BQIXo3Y8zMAKpYZmlps,324
|
25
25
|
huff/tests/data/Haslach_supermarkets.shx,sha256=j23QHX-SmdAeN04rw0x8nUOran-OCg_T6r_LvzzEPWs,164
|
26
26
|
huff/tests/data/Wieland2015.xlsx,sha256=H4rxCFlctn44-O6mIyeFf67FlgvznLX7xZqpoWYS41A,25788
|
27
|
-
huff-1.5.
|
28
|
-
huff-1.5.
|
29
|
-
huff-1.5.
|
30
|
-
huff-1.5.
|
27
|
+
huff-1.5.8.dist-info/METADATA,sha256=VBE1R3rh3RlLMGMHZfdLci6MA9HrOhLWiwm7808B8p8,6282
|
28
|
+
huff-1.5.8.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
29
|
+
huff-1.5.8.dist-info/top_level.txt,sha256=nlzX-PxZNFmIxANIJMySuIFPihd6qOBkRlhIC28NEsQ,5
|
30
|
+
huff-1.5.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|