huff 1.3.0__py3-none-any.whl → 1.3.2__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 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.3.0
8
- # Last update: 2025-05-22 05:44
7
+ # Version: 1.3.2
8
+ # Last update: 2025-05-30 11:06
9
9
  # Copyright (c) 2025 Thomas Wieland
10
10
  #-----------------------------------------------------------------------
11
11
 
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.3.0
8
- # Last update: 2025-05-22 05:45
7
+ # Version: 1.3.2
8
+ # Last update: 2025-05-30 11:06
9
9
  # Copyright (c) 2025 Thomas Wieland
10
10
  #-----------------------------------------------------------------------
11
11
 
@@ -50,11 +50,11 @@ class CustomerOrigins:
50
50
 
51
51
  return self.metadata
52
52
 
53
- def get_isochrones(self):
53
+ def get_isochrones_gdf(self):
54
54
 
55
55
  return self.isochrones_gdf
56
56
 
57
- def get_buffers(self):
57
+ def get_buffers_gdf(self):
58
58
 
59
59
  return self.buffers_gdf
60
60
 
@@ -62,7 +62,7 @@ class CustomerOrigins:
62
62
 
63
63
  metadata = self.metadata
64
64
 
65
- print("Huff Model Customer Origins")
65
+ print("Customer Origins")
66
66
  print("No. locations " + str(metadata["no_points"]))
67
67
 
68
68
  if metadata["marketsize_col"] is None:
@@ -230,11 +230,8 @@ class SupplyLocations:
230
230
 
231
231
  metadata = self.metadata
232
232
 
233
- print("Huff Model Supply Locations")
233
+ print("Supply Locations")
234
234
  print("No. locations " + str(metadata["no_points"]))
235
-
236
- if metadata["attraction_col"][0] is None or metadata["attraction_col"] == []:
237
- print("Attraction column(s) not defined")
238
235
  else:
239
236
  print("Attraction column(s) " + ",".join(metadata["attraction_col"]))
240
237
 
@@ -443,7 +440,7 @@ class InteractionMatrix:
443
440
  customer_origins_metadata = self.get_customer_origins().get_metadata()
444
441
  supply_locations_metadata = self.get_supply_locations().get_metadata()
445
442
 
446
- print("Huff Model Interaction Matrix")
443
+ print("Interaction Matrix")
447
444
  print("----------------------------------")
448
445
  print("Supply locations " + str(supply_locations_metadata["no_points"]))
449
446
  if supply_locations_metadata["attraction_col"][0] is None:
@@ -659,7 +656,7 @@ class InteractionMatrix:
659
656
 
660
657
  return self
661
658
 
662
- def marketareas (self):
659
+ def marketareas(self):
663
660
 
664
661
  interaction_matrix_df = self.interaction_matrix_df
665
662
 
@@ -679,6 +676,29 @@ class InteractionMatrix:
679
676
 
680
677
  return huff_model
681
678
 
679
+
680
+ def hansen(
681
+ self,
682
+ from_origins: bool = True
683
+ ):
684
+
685
+ interaction_matrix_df = self.interaction_matrix_df
686
+
687
+ if interaction_matrix_df["U_ij"].isna().all():
688
+ self.utility()
689
+ interaction_matrix_df = self.interaction_matrix_df
690
+
691
+ if from_origins:
692
+ hansen_df = pd.DataFrame(interaction_matrix_df.groupby("i")["U_ij"].sum()).reset_index()
693
+ hansen_df = hansen_df.rename(columns = {"U_ij": "A_i"})
694
+ else:
695
+ hansen_df = pd.DataFrame(interaction_matrix_df.groupby("j")["U_ij"].sum()).reset_index()
696
+ hansen_df = hansen_df.rename(columns = {"U_ij": "A_j"})
697
+
698
+ return hansen_df
699
+
700
+ =======
701
+ >>>>>>> e9583d0cd7f349f17f005df0fa75e70160655aa3
682
702
  def mci_transformation(
683
703
  self,
684
704
  cols: list = ["A_j", "t_ij"]
@@ -1017,8 +1037,7 @@ class MCIModel:
1017
1037
  transformation = "LCT"
1018
1038
  ):
1019
1039
 
1020
- interaction_matrix = self.interaction_matrix
1021
-
1040
+ interaction_matrix = self.interaction_matrix
1022
1041
  interaction_matrix_df = interaction_matrix.get_interaction_matrix_df()
1023
1042
 
1024
1043
  if interaction_matrix_df["t_ij"].isna().all():
@@ -1031,7 +1050,8 @@ class MCIModel:
1031
1050
  cols = ["A_j", "t_ij"]
1032
1051
  )
1033
1052
 
1034
- customer_origins_metadata = interaction_matrix.get_customer_origins().get_metadata()
1053
+ customer_origins = interaction_matrix.get_customer_origins()
1054
+ customer_origins_metadata = customer_origins.get_metadata()
1035
1055
 
1036
1056
  t_ij_weighting = customer_origins_metadata["weighting"][0]["param"]
1037
1057
 
@@ -1040,7 +1060,8 @@ class MCIModel:
1040
1060
  else:
1041
1061
  mci_formula = f"t_ij**{t_ij_weighting}"
1042
1062
 
1043
- supply_locations_metadata = interaction_matrix.get_supply_locations().get_metadata()
1063
+ supply_locations = interaction_matrix.get_supply_locations()
1064
+ supply_locations_metadata = supply_locations.get_metadata()
1044
1065
  attraction_col = supply_locations_metadata["attraction_col"]
1045
1066
  attraction_weighting = supply_locations_metadata["weighting"]
1046
1067
 
@@ -1056,17 +1077,27 @@ class MCIModel:
1056
1077
  if transformation == "ILCT":
1057
1078
  interaction_matrix_df["U_ij"] = np.exp(interaction_matrix_df["U_ij"])
1058
1079
 
1059
- self.interaction_matrix = interaction_matrix_df
1080
+ interaction_matrix = InteractionMatrix(
1081
+ interaction_matrix_df,
1082
+ customer_origins,
1083
+ supply_locations
1084
+ )
1085
+ self.interaction_matrix = interaction_matrix
1060
1086
 
1061
1087
  return self
1062
1088
 
1063
- def probabilities (self):
1089
+ def probabilities (
1090
+ self,
1091
+ transformation = "LCT"
1092
+ ):
1064
1093
 
1065
- interaction_matrix_df = self.interaction_matrix_df
1094
+ interaction_matrix = self.interaction_matrix
1095
+ interaction_matrix_df = interaction_matrix.get_interaction_matrix_df()
1066
1096
 
1067
1097
  if interaction_matrix_df["U_ij"].isna().all():
1068
- self.utility()
1069
- interaction_matrix_df = self.interaction_matrix_df
1098
+ self.utility(transformation = transformation)
1099
+ interaction_matrix = self.interaction_matrix
1100
+ interaction_matrix_df = interaction_matrix.get_interaction_matrix_df()
1070
1101
 
1071
1102
  utility_i = pd.DataFrame(interaction_matrix_df.groupby("i")["U_ij"].sum())
1072
1103
  utility_i = utility_i.rename(columns = {"U_ij": "U_i"})
@@ -1082,14 +1113,22 @@ class MCIModel:
1082
1113
 
1083
1114
  interaction_matrix_df = interaction_matrix_df.drop(columns=["U_i"])
1084
1115
 
1085
- self.interaction_matrix_df = interaction_matrix_df
1116
+ interaction_matrix.interaction_matrix_df = interaction_matrix_df
1117
+ self.interaction_matrix = interaction_matrix
1086
1118
 
1087
1119
  return self
1088
1120
 
1089
- def flows (self):
1121
+ def flows (
1122
+ self,
1123
+ transformation = "LCT"
1124
+ ):
1090
1125
 
1091
- interaction_matrix_df = self.interaction_matrix_df
1126
+ interaction_matrix = self.interaction_matrix
1127
+ interaction_matrix_df = interaction_matrix.get_interaction_matrix_df()
1092
1128
 
1129
+ if "C_i" not in interaction_matrix_df.columns:
1130
+ raise ValueError ("No market size column defined in interaction matrix.")
1131
+
1093
1132
  if interaction_matrix_df["C_i"].isna().all():
1094
1133
  raise ValueError ("Market size column in customer origins not defined. Use CustomerOrigins.define_marketsize()")
1095
1134
 
@@ -1099,8 +1138,9 @@ class MCIModel:
1099
1138
  )
1100
1139
 
1101
1140
  if interaction_matrix_df["p_ij"].isna().all():
1102
- self.probabilities()
1103
- interaction_matrix_df = self.interaction_matrix_df
1141
+ self.probabilities(transformation=transformation)
1142
+ interaction_matrix = self.interaction_matrix
1143
+ interaction_matrix_df = interaction_matrix.get_interaction_matrix_df()
1104
1144
 
1105
1145
  interaction_matrix_df["E_ij"] = interaction_matrix_df["p_ij"] * interaction_matrix_df["C_i"]
1106
1146
 
@@ -1573,7 +1613,7 @@ def get_isochrones(
1573
1613
  profile: str = "driving-car",
1574
1614
  donut: bool = True,
1575
1615
  ors_server: str = "https://api.openrouteservice.org/v2/",
1576
- ors_auth: str = None,
1616
+ ors_auth: str = None,
1577
1617
  timeout = 10,
1578
1618
  delay = 1,
1579
1619
  save_output: bool = True,
@@ -1623,6 +1663,8 @@ def get_isochrones(
1623
1663
  time.sleep(delay)
1624
1664
 
1625
1665
  isochrone_gdf[unique_id_col] = unique_id_values[i]
1666
+
1667
+ isochrone_gdf["segment_minutes"] = isochrone_gdf["segment"]/60
1626
1668
 
1627
1669
  isochrones_gdf = pd.concat(
1628
1670
  [
@@ -1634,6 +1676,9 @@ def get_isochrones(
1634
1676
 
1635
1677
  i = i+1
1636
1678
 
1679
+ isochrones_gdf["segment"] = isochrones_gdf["segment"].astype(int)
1680
+ isochrones_gdf["segment_minutes"] = isochrones_gdf["segment_minutes"].astype(int)
1681
+
1637
1682
  isochrones_gdf.set_crs(
1638
1683
  output_crs,
1639
1684
  allow_override=True,
huff/ors.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.2.1
8
- # Last update: 2025-05-22 05:46
7
+ # Version: 1.3.2
8
+ # Last update: 2025-05-30 11:07
9
9
  # Copyright (c) 2025 Thomas Wieland
10
10
  #-----------------------------------------------------------------------
11
11
 
@@ -119,7 +119,6 @@ class Client:
119
119
  def isochrone(
120
120
  self,
121
121
  locations: list,
122
- id: list = [],
123
122
  segments: list = [900, 600, 300],
124
123
  range_type: str = "time",
125
124
  intersections: str = "true",
@@ -130,6 +129,12 @@ class Client:
130
129
  output_crs: str = "EPSG:4326"
131
130
  ):
132
131
 
132
+ if len(segments) > 10:
133
+ raise ValueError ("ORS client does not allow >10 intervals in an Isochrones query. See https://openrouteservice.org/restrictions/.")
134
+
135
+ if len(locations) > 5:
136
+ raise ValueError ("ORS client does not allow >5 locations in an Isochrones query. See https://openrouteservice.org/restrictions/.")
137
+
133
138
  ors_url = self.server + "isochrones/" + profile
134
139
  auth = self.auth
135
140
 
@@ -200,7 +205,7 @@ class Client:
200
205
 
201
206
  isochrones_gdf["segment"] = 0
202
207
  isochrones_gdf_properties_dict = dict(isochrones_gdf["properties"])
203
-
208
+
204
209
  for i in range(len(isochrones_gdf_properties_dict)):
205
210
  isochrones_gdf.iloc[i,3] = isochrones_gdf_properties_dict[i]["value"]
206
211
 
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.0.0
8
- # Last update: 2025-05-22 05:46
7
+ # Version: 1.3.2
8
+ # Last update: 2025-05-30 11:07
9
9
  # Copyright (c) 2025 Thomas Wieland
10
10
  #-----------------------------------------------------------------------
11
11
 
@@ -173,9 +173,52 @@ def map_with_basemap(
173
173
  extent_img = [sw_lon, ne_lon, sw_lat, ne_lat]
174
174
  ax.imshow(img, extent=extent_img, origin="upper")
175
175
 
176
+ i = 0
176
177
  for layer in layers:
178
+
177
179
  layer_3857 = layer.to_crs(epsg=3857)
178
- layer_3857.plot(ax=ax, alpha=0.6)
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
179
222
 
180
223
  bbox = box(sw_lon, sw_lat, ne_lon, ne_lat)
181
224
  extent_geom = gpd.GeoSeries([bbox], crs=4326).to_crs(epsg=3857).total_bounds
@@ -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,8 +4,8 @@
4
4
  # Author: Thomas Wieland
5
5
  # ORCID: 0000-0001-5168-9846
6
6
  # mail: geowieland@googlemail.com
7
- # Version: 1.3.0
8
- # Last update: 2025-05-22 05:41
7
+ # Version: 1.3.2
8
+ # Last update: 2025-05-30 09:37
9
9
  # Copyright (c) 2025 Thomas Wieland
10
10
  #-----------------------------------------------------------------------
11
11
 
@@ -68,7 +68,7 @@ Haslach_supermarkets.define_attraction_weighting(
68
68
  # Define attraction weighting (gamma)
69
69
 
70
70
  Haslach_supermarkets.isochrones(
71
- segments_minutes=[5, 10],
71
+ segments_minutes=[3, 6, 9, 12, 15],
72
72
  profile = "foot-walking",
73
73
  save_output=True,
74
74
  ors_auth="5b3ce3597851110001cf62480a15aafdb5a64f4d91805929f8af6abd",
@@ -102,6 +102,19 @@ interaction_matrix = haslach_interactionmatrix.transport_costs(
102
102
  # Obtaining transport costs (default: driving-car)
103
103
  # ORS API documentation: https://openrouteservice.org/dev/#/api-docs/v2/
104
104
 
105
+ interaction_matrix.summary()
106
+ # Summary of interaction matrix
107
+
108
+ print(interaction_matrix.hansen())
109
+ # Hansen accessibility for interaction matrix
110
+
111
+ interaction_matrix = interaction_matrix.flows()
112
+ # Calculating spatial flows for interaction matrix
113
+
114
+ huff_model = interaction_matrix.marketareas()
115
+ # Calculating total market areas for interaction matrix
116
+ # Result of class HuffModel
117
+
105
118
  interaction_matrix = interaction_matrix.flows()
106
119
  # Calculating spatial flows
107
120
 
@@ -170,22 +183,44 @@ Wieland2015_fit.summary()
170
183
  # Buffer analysis:
171
184
 
172
185
  Haslach_supermarkets_gdf = Haslach_supermarkets.get_geodata_gpd_original()
173
- Haslach_buffers = Haslach_buf.get_buffers()
186
+ Haslach_buffers = Haslach_buf.get_buffers_gdf()
174
187
  # Extracting points and buffer polygons
175
188
 
176
- spj_test = point_spatial_join(
189
+ Haslach_districts_buf = point_spatial_join(
177
190
  polygon_gdf = Haslach_buffers,
178
191
  point_gdf = Haslach_supermarkets_gdf,
179
192
  polygon_ref_cols = ["BEZEICHN", "segment"],
180
193
  point_stat_col = "VKF_qm"
181
194
  )
182
195
  # Spatial join with buffers and points
183
- # Statistics for supermarkets by statistical districts
196
+ # Statistics for supermarket selling space by buffers of statistical districts
197
+ # (How much selling space in 500, 1000, and 1500 metres?)
198
+
199
+ Haslach_districts_buf[0].to_file("Haslach_districts_buf.shp")
200
+ # Save joined points as shapefile
201
+
202
+ print(Haslach_districts_buf[1])
203
+ # Showing df with overlay statistics
204
+
205
+
206
+ # Isochrones analysis:
207
+
208
+ Haslach_districts = Haslach.get_geodata_gpd_original()
209
+
210
+ Haslach_supermarkets_iso = point_spatial_join(
211
+ polygon_gdf = Haslach_supermarkets_isochrones,
212
+ point_gdf = Haslach_districts,
213
+ polygon_ref_cols = ["LFDNR", "segment"],
214
+ point_stat_col = "pop"
215
+ )
216
+ # Spatial join with isochrones and points
217
+ # Statistics for population by isochrones of supermarkets
218
+ # (How much population in 5, 10, and 15 minutes?)
184
219
 
185
- spj_test[0].to_file("spj_test.shp")
220
+ Haslach_supermarkets_iso[0].to_file("Haslach_supermarkets_iso.shp")
186
221
  # Save joined points as shapefile
187
222
 
188
- print(spj_test[1])
223
+ print(Haslach_supermarkets_iso[1])
189
224
  # Showing df with overlay statistics
190
225
 
191
226
 
@@ -202,6 +237,37 @@ map_with_basemap(
202
237
  Haslach_gdf,
203
238
  Haslach_supermarkets_gdf
204
239
  ],
240
+ styles={
241
+ 0: {
242
+ "color": {
243
+ "segment_minutes": {
244
+ "3": "green",
245
+ "6": "yellow",
246
+ "9": "orange",
247
+ "12": "red",
248
+ "13": "darkred"
249
+ }
250
+ },
251
+ "alpha": 0.3
252
+ },
253
+ 1: {
254
+ "color": "green",
255
+ "alpha": 1
256
+ },
257
+ 2: {
258
+ "color": {
259
+ "Name": {
260
+ "Aldi S├╝d": "blue",
261
+ "Edeka": "yellow",
262
+ "Lidl": "red",
263
+ "Netto": "orange",
264
+ "Real": "black",
265
+ "Treff 3000": "fuchsia"
266
+ }
267
+ },
268
+ "alpha": 1
269
+ }
270
+ },
205
271
  output_filepath = "Haslach_map.png"
206
272
  )
207
273
  # Map with three layers and OSM basemap
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: huff
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: huff: Huff Model Market Area Analysis
5
5
  Author: Thomas Wieland
6
6
  Author-email: geowieland@googlemail.com
@@ -15,9 +15,18 @@ Requires-Dist: matplotlib
15
15
  Requires-Dist: pillow
16
16
  Requires-Dist: contextily
17
17
  Requires-Dist: openpyxl
18
+ Dynamic: author
19
+ Dynamic: author-email
20
+ Dynamic: description
21
+ Dynamic: description-content-type
22
+ Dynamic: requires-dist
23
+ Dynamic: summary
18
24
 
19
25
  # huff: Huff Model Market Area Analysis
20
26
 
27
+ This Python library is designed for performing market area analyses with the Huff Model (Huff 1962, 1964) and/or the Multiplicative Competitive Interaction (MCI) Model (Nakanishi and Cooper 1974, 1982). Users may load point shapefiles (or CSV, XLSX) of customer origins and supply locations and conduct a market area analysis step by step. The package also includes supplementary GIS functions, including clients for OpenRouteService(1) for network analysis (e.g., transport cost matrix) and OpenStreetMap(2) for simple maps. See Huff and McCallum (2008) or Wieland (2017) for a description of the models and their practical application.
28
+
29
+
21
30
  ## Author
22
31
 
23
32
  Thomas Wieland [ORCID](https://orcid.org/0000-0001-5168-9846) [EMail](mailto:geowieland@googlemail.com)
@@ -36,23 +45,26 @@ See the /tests directory for usage examples of most of the included functions.
36
45
  - Fitting MCI model with >= 2 independent variables
37
46
  - MCI model market simulation
38
47
  - **GIS tools**:
39
- - OpenRouteService Client (1):
48
+ - OpenRouteService(1) Client:
40
49
  - Creating transport costs matrix from origins and destinations
41
50
  - Creating isochrones from origins and destinations
42
- - OpenStreetMap Client (2):
51
+ - OpenStreetMap(2) Client:
43
52
  - Creating simple maps with OSM basemap
44
53
  - Other GIS tools:
45
54
  - Creating buffers from geodata
46
55
  - Spatial join with with statistics
47
56
  - Creating euclidean distance matrix from origins and destinations
48
57
  - Overlay-difference analysis of polygons
58
+ - Hansen accessibility
49
59
  - **Data management tools**:
50
60
  - Loading own interaction matrix for analysis
51
61
  - Creating origins/destinations objects from point geodata
52
62
 
53
63
  (1) © openrouteservice.org by HeiGIT | Map data © OpenStreetMap contributors | https://openrouteservice.org/
64
+
54
65
  (2) © OpenStreetMap contributors | available under the Open Database License | https://www.openstreetmap.org/
55
66
 
67
+
56
68
  ## Literature
57
69
  - Huff DL (1962) *Determination of Intra-Urban Retail Trade Areas*.
58
70
  - Huff DL (1964) Defining and estimating a trading area. *Journal of Marketing* 28(4): 34–38. [10.2307/1249154](https://doi.org/10.2307/1249154)
@@ -1,24 +1,24 @@
1
1
  huff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- huff/gistools.py,sha256=yEinx-rq9o8yXW6CgVIVsb1Rwi0WKnvCDRGD52h8jxo,6857
3
- huff/models.py,sha256=hqR3jcFBD2h3SPX_U7YApQrql7yBUw2frmLF4uhVTfI,59645
4
- huff/ors.py,sha256=D38PN2Az9kUbAqsFpb4W5Z5pSkbKSOH1W5IBlhos7ZA,11570
5
- huff/osm.py,sha256=mj9FFxieMd5Gv8Nn72nt89dHSfzZYGQt2dYHOWfx6hY,5464
2
+ huff/gistools.py,sha256=Zcckofl4OWueSXZmUHRsbrLvAHZuxeI5Gwh4g7hi2H0,6857
3
+ huff/models.py,sha256=gSVZims_7TvXr-pzUYAP19fnEtHmr0QmT8LFI_LsIVw,61452
4
+ huff/ors.py,sha256=hjLJzBBQUP0frF-Aj8aCkOFVeCHc_3rGkJ8srOYIPOE,11929
5
+ huff/osm.py,sha256=KTJo9NYDNZjDDYp4FKNH0MxMnY9A7kfg12FvCAvpmQw,6858
6
6
  huff/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- huff/tests/tests_huff.py,sha256=kWUob6X7DvznSo6Nh9yaffkbdEt7XUK1s-JOVZBLz2g,5588
7
+ huff/tests/tests_huff.py,sha256=iITEaA96WrewlLyC3l8wWPMwVigAgG9OYsHbix7hI0A,7595
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
11
- huff/tests/data/Haslach.qmd,sha256=j9i4_Pz7ZMSG2UDSb3nuhJpw0KWXIRhiiDymqJP6_Fo,2479
11
+ huff/tests/data/Haslach.qmd,sha256=JlcOYzG4vI1NH1IuOpxwIPnJsCyC-pDRAI00TzEvNf0,2522
12
12
  huff/tests/data/Haslach.shp,sha256=s7ks-ukOIKMJCD5x6m0MO6pwkg1USvhudQKTg74ib1E,212
13
13
  huff/tests/data/Haslach.shx,sha256=VEMghRPP_HUYIuGoxR7X0eHQe9LnO4s8JP4twfzKyyk,132
14
14
  huff/tests/data/Haslach_supermarkets.cpg,sha256=OtMDH1UDpEBK-CUmLugjLMBNTqZoPULF3QovKiesmCQ,5
15
15
  huff/tests/data/Haslach_supermarkets.dbf,sha256=4fTBxntDvQ8qFPdGK82ywJd2Xq_9nApDyi3h5_KPFSc,21282
16
16
  huff/tests/data/Haslach_supermarkets.prj,sha256=2Jy1Vlzh7UxQ1MXpZ9UYLs2SxfrObj2xkEkZyLqmGTY,437
17
- huff/tests/data/Haslach_supermarkets.qmd,sha256=j9i4_Pz7ZMSG2UDSb3nuhJpw0KWXIRhiiDymqJP6_Fo,2479
17
+ huff/tests/data/Haslach_supermarkets.qmd,sha256=JlcOYzG4vI1NH1IuOpxwIPnJsCyC-pDRAI00TzEvNf0,2522
18
18
  huff/tests/data/Haslach_supermarkets.shp,sha256=X7QbQ0BTMag_B-bDRbpr-go2BQIXo3Y8zMAKpYZmlps,324
19
19
  huff/tests/data/Haslach_supermarkets.shx,sha256=j23QHX-SmdAeN04rw0x8nUOran-OCg_T6r_LvzzEPWs,164
20
20
  huff/tests/data/Wieland2015.xlsx,sha256=SaVM-Hi5dBTmf2bzszMnZ2Ec8NUE05S_5F2lQj0ayS0,19641
21
- huff-1.3.0.dist-info/METADATA,sha256=4qAsnzZ3lkPANRMjBrkPhj61HoBfC7cHkambNIZjSfI,3884
22
- huff-1.3.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
23
- huff-1.3.0.dist-info/top_level.txt,sha256=nlzX-PxZNFmIxANIJMySuIFPihd6qOBkRlhIC28NEsQ,5
24
- huff-1.3.0.dist-info/RECORD,,
21
+ huff-1.3.2.dist-info/METADATA,sha256=cf90Dvp0NVO4ZoUx1uBT90ahuAmKKHloGd_z4ne4-I8,4697
22
+ huff-1.3.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
23
+ huff-1.3.2.dist-info/top_level.txt,sha256=nlzX-PxZNFmIxANIJMySuIFPihd6qOBkRlhIC28NEsQ,5
24
+ huff-1.3.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.45.1)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5