hmi 0.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.
hmi/__init__.py ADDED
@@ -0,0 +1,53 @@
1
+ # -*- coding: utf-8 -*-
2
+ """The HMI package is a python module that provides an interface to compute distances between locations based on Özak's (2010, 2012, 2015) Human Mobility Index."""
3
+
4
+ #import metadata
5
+ from .metadata import *
6
+ from .hmi import wgs84, cea, HMI, HMISea, path
7
+ import os
8
+ import requests
9
+ from tqdm import tqdm
10
+
11
+ __version__ = version
12
+ __author__ = authors[0]
13
+ __license__ = license
14
+ __copyright__ = copyright
15
+
16
+ def download_file_with_progress(url, filename):
17
+ """Download a file with a progress bar."""
18
+ response = requests.get(url, stream=True)
19
+ total_size = int(response.headers.get('content-length', 0))
20
+ block_size = 1024 # 1 Kibibyte
21
+
22
+ # Use tqdm for the progress bar
23
+ with tqdm(total=total_size, unit='iB', unit_scale=True, desc=f"Downloading {filename}") as t:
24
+ with open(filename, 'wb') as f:
25
+ for data in response.iter_content(block_size):
26
+ t.update(len(data))
27
+ f.write(data)
28
+
29
+ # Download data if needed
30
+ def download():
31
+ path = os.path.dirname(os.path.realpath(__file__)) + '/data/'
32
+ os.makedirs(path, exist_ok=True)
33
+
34
+ files = {
35
+ 'HMI.tif': 'https://zenodo.org/records/14285746/files/HMI.tif?download=1',
36
+ 'HMISea.tif': 'https://zenodo.org/records/14285746/files/HMISea.tif?download=1',
37
+ 'HMI10.tif': 'https://zenodo.org/records/14285746/files/HMI10.tif?download=1',
38
+ 'HMISea10.tif': 'https://zenodo.org/records/14285746/files/HMISea10.tif?download=1',
39
+ }
40
+
41
+ for filename, url in files.items():
42
+ dest_path = os.path.join(path, filename)
43
+ if not os.path.exists(dest_path):
44
+ download_file_with_progress(url, dest_path)
45
+
46
+ # Check whether data is present or not
47
+ path = os.path.dirname(os.path.realpath(__file__)) + '/data/'
48
+ if not os.path.exists(path + 'HMI10.tif'):
49
+ print('Downloading required HMI datasets.')
50
+ print('This may take a few minutes.')
51
+ print('This will be done only once.')
52
+ print('Please be patient.')
53
+ download()
hmi/hmi.py ADDED
@@ -0,0 +1,490 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+ '''
4
+ ======================================================
5
+ Author: Ömer Özak, 2014 (ozak at smu.edu)
6
+ Website: http://omerozak.com
7
+ GitHub: https://github.com/ozak/
8
+ ======================================================
9
+ Package to compute distances using HMI, HMISea and HMIOcean
10
+ '''
11
+ from __future__ import division
12
+ import sys, os, time
13
+ from osgeo import gdal, gdalnumeric, ogr, osr
14
+ from osgeo.gdalconst import *
15
+ import numpy as np
16
+ import pandas as pd
17
+ from rasterstats import zonal_stats
18
+ import pysal as ps
19
+ import shapely
20
+ from shapely.geometry import Polygon, Point, LineString
21
+ from shapely.wkt import loads, dumps
22
+ from shapely.ops import cascaded_union
23
+ import rasterio.features
24
+ from affine import Affine
25
+ from shapely.geometry import shape
26
+ import georasters as gr
27
+ import pyproj
28
+ from pyproj import CRS
29
+ import geopandas as gp
30
+ from geopy.distance import great_circle
31
+ import skimage.graph as graph
32
+ import multiprocessing as mp
33
+ from multiprocessing import Pool, Lock
34
+ import warnings
35
+ from fiona.crs import from_string
36
+ from skimage.segmentation import watershed
37
+
38
+ path=os.path.dirname(os.path.abspath(__file__))+'/data/'
39
+ cea2 = from_string('+proj=cea +lon_0=0 +lat_ts=0 +x_0=0 +y_0=0 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs')
40
+ wgs84 = CRS("EPSG:4326")
41
+ cea = CRS("ESRI:54034")
42
+
43
+ class HMI(object):
44
+ """
45
+ Implements the HMI Class, which will be used to construct the computations, etc. and hold everything in a unique object.
46
+
47
+ Usage:
48
+ Load data
49
+ A = HMI(sources, destinations, lat=None, lon=None, x=None, y=None, projected=None, fast=True)
50
+ B = HMI(sources, destinations, lat='lat', lon='lon')
51
+ C = HMI(sources, destinations, y='y', x='x')
52
+
53
+ A.correct_all()
54
+ A.HMIdistance(export_shape=True, export_raster=True)
55
+ B.HMIdistance()
56
+ B.hmidist
57
+
58
+ where
59
+ sources: (Geo)Pandas DataFrame with the locations of the sources from which to compute distances
60
+ should have columns with either X/Y or LAT/LON
61
+ destinations: (Geo)Pandas DataFrame with the locations of the destinations to which to compute distances
62
+ should have columns with either X/Y or LAT/LON
63
+ lat: Name of column with latitudes (if applicable)
64
+ lon: Name of column with longitudes (if applicable)
65
+ y: Name of column with cylindrical equal area projected latitudes (if applicable)
66
+ x: Name of column with cylindrical equal area projected longitudes (if applicable)
67
+ projected: Boolean, references is data is projected or not
68
+ fast: Use small cost surface or not
69
+ file: Type of cost
70
+ clip: Polygon or GeoPandas DataFrame to clip region for faster local analysis
71
+ """
72
+ # Initialize Object
73
+ def __init__(self, sources, destinations, lat=None, lon=None, x=None, y=None, projected=None, fast=True, file='HMI', clip=None):
74
+ super(HMI, self).__init__()
75
+ self.fast = fast
76
+ self.clip = clip
77
+ self.projected = projected
78
+ self.costs = None
79
+ self.mcp_cost = None
80
+ self.start_pointscols = sources.columns.values
81
+ self.end_pointscols = destinations.columns.values
82
+ if fast:
83
+ self.file = file+'10'
84
+ else:
85
+ self.file = file
86
+ if not isinstance(sources, pd.core.frame.DataFrame) and not isinstance(sources, gp.geodataframe.GeoDataFrame):
87
+ raise TypeError('Sources has to be a (Geo)Pandas Data Frame Object.')
88
+ self.sources = sources.copy()
89
+ if not isinstance(destinations, pd.core.frame.DataFrame) and not isinstance(destinations, gp.geodataframe.GeoDataFrame):
90
+ raise TypeError('Destinations has to be a (Geo)Pandas Data Frame Object.')
91
+ self.destinations = destinations.copy()
92
+ if isinstance(self.destinations, gp.geodataframe.GeoDataFrame):
93
+ projd = self.destinations.crs
94
+ else:
95
+ projd = None
96
+ if isinstance(self.sources, gp.geodataframe.GeoDataFrame):
97
+ projs=self.sources.crs
98
+ else:
99
+ projs = None
100
+ if projd==projs and projd==cea:
101
+ self.projected = True
102
+ else:
103
+ self.projected = False
104
+ if lat!=None and lon!=None:
105
+ self.projected = False
106
+ elif x!=None and y!=None:
107
+ self.projected = True
108
+ if not self.projected:
109
+ warnings.warn('No projection assigned. Program will check Sources and Destinations for clues.\
110
+ If lat/lon in these data frames, it assumes unprojected. If X/Y in these data frames, then it assumes projected (CEA)')
111
+ lat = self.sources.T.index[self.sources.T.reset_index()['index'].apply(lambda x: x.upper().strip().find('LAT')!=-1)].values
112
+ lon = self.sources.T.index[self.sources.T.reset_index()['index'].apply(lambda x: x.upper().strip().find('LON')!=-1)].values
113
+ latlon = lat.shape[0]+lon.shape[0]
114
+ if latlon>2:
115
+ warnings.warn('More than one columns with words "LAT" or "LON". Chose first occurrence for LAT/LON.\
116
+ If this is not what you intended, consider assigning `lat` and `lon` strings.')
117
+ if latlon>1:
118
+ self.projected = False
119
+ lat = lat[0]
120
+ lon = lon[0]
121
+ else:
122
+ y = self.sources.T.index[self.sources.T.reset_index()['index'].apply(lambda x: x.upper().strip().find('X')!=-1)].values
123
+ x = self.sources.T.index[self.sources.T.reset_index()['index'].apply(lambda x: x.upper().strip().find('Y')!=-1)].values
124
+ xy = x.shape[0]+y.shape[0]
125
+ if xy>2:
126
+ warnings.warn('More than one columns with words "X" or "Y". Chose first occurrence for X/Y.\
127
+ If this is not what you intended, consider assigning `x` and `y` strings.')
128
+ if xy>1:
129
+ self.projected =True
130
+ else:
131
+ raise SyntaxError('Could not determine projection')
132
+ if self.projected==False:
133
+ # Convert project to equal cylindrical area
134
+ p = pyproj.Proj(proj='cea', ellps='WGS84')
135
+ # p(lon,lat)
136
+ [x, y] = p(self.sources[lon].values,self.sources[lat].values)
137
+ self.sources['x'] = x
138
+ self.sources['y'] = y
139
+ [x, y] = p(self.destinations[lon].values,self.destinations[lat].values)
140
+ self.destinations['x'] = x
141
+ self.destinations['y'] = y
142
+ else:
143
+ self.sources['x'] = self.sources[x]
144
+ self.sources['y'] = self.sources[y]
145
+ self.destinations['x'] = self.destinations[x]
146
+ self.destinations['y'] = self.destinations[y]
147
+ self.sources[self.file + 'ID'] = self.sources.index.values
148
+ self.destinations[self.file + 'ID'] = self.destinations.index.values + self.sources[self.file + 'ID'].max() + 1
149
+ self.load_data()
150
+ #############################
151
+ # Set up useful functions
152
+ #############################
153
+ # Load cost surface
154
+ def load_data(self):
155
+ """
156
+ Loads Cost surface as GeoRaster into object's ``costs`` attribute.
157
+
158
+ Usage:
159
+ A.load_data()
160
+ """
161
+ if self.fast:
162
+ self.mask = gr.from_file(path+'HMI10.tif')
163
+ else:
164
+ self.mask = gr.from_file(path+'HMI.tif')
165
+ self.costs = gr.from_file(path+self.file+'.tif')
166
+ if self.clip is not None:
167
+ self.costs = self.costs.clip(self.clip)[0]
168
+ self.mask = self.mask.clip(self.clip)[0]
169
+ self.mask = self.mask.raster.mask
170
+ original_mask = self.costs.raster.mask
171
+ self.costs.raster.data[original_mask] = -9
172
+ self.costs.nodata_value = -9
173
+ self.sources['row'], self.sources['col'] = self.costs.map_pixel_location(self.sources.x, self.sources.y)
174
+ self.destinations['row'], self.destinations['col'] = self.costs.map_pixel_location(self.destinations.x, self.destinations.y)
175
+ pass
176
+ # Show cost on HMI cost surface of points at (x,y)
177
+ def costs_points(self, x_points, y_points):
178
+ """
179
+ Show costs at points. Useful to see if any points are outside the costs' surface area.
180
+ Returns a masked Numpy array with the costs
181
+
182
+ Usage:
183
+ A.costs_points(A.sources.x, A.sources.y)
184
+ """
185
+ if not self.costs:
186
+ self.load_data()
187
+ return self.costs.map_pixel(x_points, y_points)
188
+ # Costs for sources
189
+ def costs_sources(self):
190
+ """
191
+ Compute costs at sources. Useful to see if any sources are outside the costs' surface area.
192
+ Adds columns to sources with costs
193
+
194
+ Usage: A.costs_sources()
195
+ """
196
+ self.sources[self.file+'costs'] = self.costs_points(self.sources.x, self.sources.y)
197
+ self.sources.loc[self.sources[self.file+'costs']==self.costs.nodata_value, self.file+'costs']=np.nan
198
+ pass
199
+ # Costs for destinations
200
+ def costs_destinations(self):
201
+ """
202
+ Compute costs at destinations. Useful to see if any destinations are outside the costs' surface area.
203
+ Adds columns to destinations with costs
204
+
205
+ Usage: A.costs_destinations()
206
+ """
207
+ self.destinations[self.file+'costs'] = self.costs_points(self.destinations.x, self.destinations.y)
208
+ self.destinations.loc[self.destinations[self.file+'costs']==self.costs.nodata_value, self.file+'costs']=np.nan
209
+ pass
210
+ # Run both functions
211
+ def costs_all(self):
212
+ """
213
+ Compute costs at both sources and destinations
214
+
215
+ Usage: A.costs_all()
216
+ """
217
+ self.costs_sources()
218
+ self.costs_destinations()
219
+ pass
220
+ # Correct bad locations
221
+ def correct_sources(self, maxmove=100, keep=True):
222
+ """
223
+ Move location of sources outside of costs surface up to ``maxmove`` cells in order to have location on cost surface.
224
+ Usage:
225
+ A.correct_destinations(maxmove=100, keep=True)
226
+ B.correct_destinations(maxmove=20, keep=False)
227
+ C.correct_destinations()
228
+ where
229
+ maxmove: Maximum number of cells to move around each location
230
+ keep: Boolean (default True) to create new columns ``originalrow`` and ``originalcol`` in DataFrame to keep original values
231
+ """
232
+ if keep:
233
+ self.sources['originalrow'] = self.sources['row']
234
+ self.sources['originalcol'] = self.sources['col']
235
+ if (self.file+'costs' not in self.sources.columns):
236
+ self.costs_sources()
237
+ problem = self.sources[self.sources[self.file+'costs'].isnull()]
238
+ rows, cols = self.costs.shape
239
+ for i in problem.iterrows():
240
+ xmin = max(i[1].col - maxmove,0)
241
+ xmax = min(i[1].col + maxmove,cols)
242
+ ymin = max(i[1].row - maxmove,0)
243
+ ymax = min(i[1].row + maxmove,rows)
244
+ costn = self.costs[ymin:ymax, xmin:xmax]
245
+ if not isinstance(costn.max(),np.ma.core.MaskedConstant):
246
+ costn = pd.DataFrame(costn>=0).stack().reset_index()
247
+ costn.columns = ['row','col','value']
248
+ costn['row'] = costn['row'] + ymin
249
+ costn['col'] = costn['col'] + xmin
250
+ costn['dist'] = (costn.row-i[1].row)**2 + (costn.col-i[1].col)**2
251
+ costn = costn.loc[costn.dist==costn.dist.min()]
252
+ self.sources.loc[i[0],'row'] = costn.row.values[0]
253
+ self.sources.loc[i[0],'col'] = costn.col.values[0]
254
+ pass
255
+ def correct_destinations(self, maxmove=100, keep=True):
256
+ """
257
+ Move location of destinations outside of costs surface up to ``maxmove`` cells in order to have location on cost surface.
258
+ Usage:
259
+ A.correct_destinations(maxmove=100, keep=True)
260
+ B.correct_destinations(maxmove=20, keep=False)
261
+ C.correct_destinations()
262
+ where
263
+ maxmove: Maximum number of cells to move around each location
264
+ keep: Boolean (default True) to create new columns ``originalrow`` and ``originalcol`` in DataFrame to keep original values
265
+ """
266
+ if keep:
267
+ self.destinations['originalrow'] = self.destinations['row']
268
+ self.destinations['originalcol'] = self.destinations['col']
269
+ if (self.file+'costs' not in self.destinations.columns):
270
+ self.costs_destinations()
271
+ problem = self.destinations[self.destinations[self.file+'costs'].isnull()]
272
+ rows, cols = self.costs.shape
273
+ for i in problem.iterrows():
274
+ xmin = max(i[1].col - maxmove,0)
275
+ xmax = min(i[1].col + maxmove,cols)
276
+ ymin = max(i[1].row - maxmove,0)
277
+ ymax = min(i[1].row + maxmove,rows)
278
+ costn = self.costs[ymin:ymax, xmin:xmax]
279
+ if not isinstance(costn.max(),np.ma.core.MaskedConstant):
280
+ costn = pd.DataFrame(costn>=0).stack().reset_index()
281
+ costn.columns = ['row','col','value']
282
+ costn['row'] = costn['row'] + ymin
283
+ costn['col'] = costn['col'] + xmin
284
+ costn['dist'] = (costn.row-i[1].row)**2 + (costn.col-i[1].col)**2
285
+ costn = costn.loc[costn.dist==costn.dist.min()]
286
+ self.destinations.loc[i[0],'row'] = costn.row.values[0]
287
+ self.destinations.loc[i[0],'col'] = costn.col.values[0]
288
+ pass
289
+ def correct_data(self, maxmove=100, keep=True):
290
+ """
291
+ Move location of sources and destinations outside of costs surface up to ``maxmove`` cells in order to have location on cost surface.
292
+ Usage:
293
+ A.correct_data(maxmove=100, keep=True)
294
+ B.correct_data(maxmove=20, keep=False)
295
+ C.correct_data()
296
+ where
297
+ maxmove: Maximum number of cells to move around each location
298
+ keep: Boolean (default True) to create new columns ``originalrow`` and ``originalcol`` in DataFrame to keep original values
299
+ """
300
+ self.correct_sources(maxmove=maxmove, keep=keep)
301
+ self.correct_destinations(maxmove=maxmove, keep=keep)
302
+ pass
303
+ # Setup Graph for distance computations and provide distance functions
304
+ def mcp(self, *args, **kwargs):
305
+ """
306
+ Setup MCP_Geometric object from skimage for optimal travel time computations
307
+ """
308
+ # Create Cost surface to work on
309
+ self.mcp_cost=graph.MCP_Geometric(self.costs.raster, *args, **kwargs)
310
+ pass
311
+ # Determine minimum travel cost to each location
312
+ def HMIdistance(self, isolation=True, export_raster=False, export_shape=False, routes=False, path='./'):
313
+ """
314
+ Compute HMI based travel distance measured in weeks of travel from each start point to all end points.
315
+ The function returns the distances between the start point and the end points as a Pandas dataframe.
316
+ Additionally, for each start point it computes the level of isolation, i.e. its average travel distance to
317
+ all other location on land
318
+ """
319
+ start_points=self.sources
320
+ end_points=self.destinations
321
+ if not self.mcp_cost:
322
+ self.mcp()
323
+ count=0
324
+ for i in start_points.iterrows():
325
+ cumulative_costs, traceback = self.mcp_cost.find_costs([[i[1].row,i[1].col]])
326
+ dist=cumulative_costs[end_points.row.values,end_points.col.values].transpose()/(7*24)
327
+ df2=pd.DataFrame(np.array([(i[1][self.file+'ID']*np.ones_like(dist)).flatten(),
328
+ end_points[self.file+'ID'],dist.flatten()]).transpose(),
329
+ columns=[self.file+'ID1',self.file+'ID2',self.file+'dist'])
330
+ # Keep only locations that are accessible
331
+ df2 = df2.loc[df2[self.file+'dist']<np.inf]
332
+ if isolation:
333
+ hmiisolation = np.ma.masked_array(cumulative_costs, mask = np.logical_or(self.mask, cumulative_costs==np.inf)
334
+ , fill_value=np.nan).mean()/(7*24)
335
+ start_points.loc[i[0],self.file+'Iso'] = hmiisolation
336
+ if export_raster:
337
+ cumulative_costs = gr.GeoRaster(np.ma.masked_array(cumulative_costs,
338
+ mask = np.logical_or(self.costs.raster.mask, cumulative_costs==np.inf),
339
+ fill_value = np.nan), self.costs.geot, self.costs.nodata_value,
340
+ projection=self.costs.projection, datatype = self.costs.datatype)
341
+ cumulative_costs.raster.data[cumulative_costs.raster.mask] = cumulative_costs.nodata_value
342
+ cumulative_costs.to_tiff(path+self.file+'_ID'+str(i[1][self.file+'ID']))
343
+ if df2.size>0:
344
+ if export_shape:
345
+ routes=True
346
+ if routes:
347
+ df2['geometry'] = df2[self.file+'ID2'].apply(lambda x:
348
+ self.mcp_cost.traceback(end_points.loc[end_points[self.file+'ID']==x][['row','col']].values[0]))
349
+ df2['geometry'] = df2.geometry.apply(lambda x: [gr.map_pixel_inv(y[0],y[1], self.costs.geot[1],
350
+ self.costs.geot[-1], self.costs.geot[0], self.costs.geot[-3]) for y in x])
351
+ df2['geometry'] = df2.geometry.apply(lambda x: LineString(x) if int(len(x)>1) else LineString([x[0],x[0]]))
352
+ df2 = gp.GeoDataFrame(df2, crs = cea)
353
+ if isolation:
354
+ df2[self.file+'Iso'] = hmiisolation
355
+ if count==0:
356
+ self.hmidist = df2.copy()
357
+ else:
358
+ self.hmidist = pd.concat([self.hmidist, df2])
359
+ count+=1
360
+ if routes:
361
+ self.hmidist = gp.GeoDataFrame(self.hmidist, crs = cea)
362
+ if export_shape:
363
+ if 'geometry' in self.end_pointscols:
364
+ self.hmidist = pd.merge(self.hmidist, end_points[[self.file+'ID']+self.end_pointscols.tolist()].drop('geometry', axis=1), left_on=self.file+'ID2', right_on=self.file+'ID', how='left')
365
+ else:
366
+ self.hmidist = pd.merge(self.hmidist, end_points[[self.file+'ID']+self.end_pointscols.tolist()], left_on=self.file+'ID2', right_on=self.file+'ID', how='left')
367
+ if 'geometry' in self.start_pointscols:
368
+ self.hmidist = pd.merge(self.hmidist, start_points[[self.file+'ID']+self.start_pointscols.tolist()].drop('geometry', axis=1), left_on=self.file+'ID1', right_on=self.file+'ID', how='left',
369
+ suffixes = ['_2','_1'])
370
+ else:
371
+ self.hmidist = pd.merge(self.hmidist, start_points[[self.file+'ID']+self.start_pointscols.tolist()], left_on=self.file+'ID1', right_on=self.file+'ID', how='left',
372
+ suffixes = ['_2','_1'])
373
+ self.hmidist = gp.GeoDataFrame(self.hmidist, crs = cea)
374
+ self.hmidist.to_file(path+self.file+'_routes.geojson', driver="GeoJSON")
375
+ pass
376
+ # Create Voronoi
377
+ def voronoi(self):
378
+ if not self.mcp_cost:
379
+ self.mcp()
380
+ mat_sources = np.ones(self.costs.shape, dtype=int) * 0
381
+ for i in self.sources.iterrows():
382
+ mat_sources[i[1].row, i[1].col] = i[1][self.file + 'ID'] + 1
383
+ cumulative_costs, traceback = self.mcp_cost.find_costs(self.sources[['row', 'col']].values)
384
+ mask = np.less(cumulative_costs, np.inf)
385
+ allo = watershed(cumulative_costs, mat_sources, mask=mask)
386
+ allo = allo - 1
387
+ allo[allo==-1] = self.costs.nodata_value
388
+ voronoi_raster = gr.GeoRaster(allo, self.costs.geot, nodata_value=self.costs.nodata_value, projection=self.costs.projection, datatype=self.costs.datatype)
389
+ voronoi = []
390
+ for shp, val in rasterio.features.shapes(allo, transform=Affine.from_gdal(*self.costs.geot)):
391
+ voronoi.append([val, shape(shp)])
392
+ voronoi = gp.GeoDataFrame(voronoi, crs=cea, columns=[self.file + 'ID', 'geometry'])
393
+ voronoi = voronoi.merge(self.sources[self.sources.columns.difference(['geometry'])], on=self.file + 'ID')
394
+ return voronoi
395
+
396
+ # Implement HMISea class, whcih is basically the same except for the raster being used
397
+ class HMISea(HMI):
398
+ """
399
+ Implements the HMISea Class, which will be used to construct the computations, etc. and hold everything in a unique object.
400
+
401
+ Usage:
402
+ Load data
403
+ A = HMISea(sources, destinations, lat=None, lon=None, x=None, y=None, projected=None, fast=True)
404
+ B = HMISea(sources, destinations, lat='lat', lon='lon')
405
+ C = HMISea(sources, destinations, y='y', x='x')
406
+
407
+ A.correct_all()
408
+ A.HMIdistance(export_shape=True, export_raster=True)
409
+ B.HMIdistance()
410
+ B.hmidist
411
+
412
+ where
413
+ sources: (Geo)Pandas DataFrame with the locations of the sources from which to compute distances
414
+ should have columns with either X/Y or LAT/LON
415
+ destinations: (Geo)Pandas DataFrame with the locations of the destinations to which to compute distances
416
+ should have columns with either X/Y or LAT/LON
417
+ lat: Name of column with latitudes (if applicable)
418
+ lon: Name of column with longitudes (if applicable)
419
+ y: Name of column with cylindrical equal area projected latitudes (if applicable)
420
+ x: Name of column with cylindrical equal area projected longitudes (if applicable)
421
+ projected: Boolean, references is data is projected or not
422
+ fast: Use small cost surface or not
423
+ """
424
+ # Initialize Object
425
+ def __init__(self, sources, destinations, lat=None, lon=None, x=None, y=None, projected=None, fast=True, file='HMISea', clip=None):
426
+ super(HMISea, self).__init__(sources, destinations, lat=lat, lon=lon, x=x, y=y, projected=projected, fast=fast, file='HMISea', clip=clip)
427
+ pass
428
+ def HMISeadistance(self, isolation=True, export_raster=False, export_shape=False, routes=False, path='./'):
429
+ """
430
+ Compute HMI based travel distance measured in weeks of travel from each start point to all end points.
431
+ The function returns the distances between the start point and the end points as a Pandas dataframe.
432
+ Additionally, for each start point it computes the level of isolation, i.e. its average travel distance to
433
+ all other location on land
434
+ """
435
+ self.HMIdistance(isolation=isolation, export_raster=export_raster, export_shape=export_shape, routes=routes, path=path)
436
+ self.hmiseadist = self.hmidist
437
+ pass
438
+
439
+ '''
440
+ # Implement HMIOcean class, whcih is basically the same except for the raster being used
441
+ class HMIOcean(HMI):
442
+ """
443
+ Implements the HMISea Class, which will be used to construct the computations, etc. and hold everything in a unique object.
444
+
445
+ Usage:
446
+ Load data
447
+ A = HMISea(sources, destinations, lat=None, lon=None, x=None, y=None, projected=None, fast=True)
448
+ B = HMISea(sources, destinations, lat='lat', lon='lon')
449
+ C = HMISea(sources, destinations, y='y', x='x')
450
+
451
+ A.correct_all()
452
+ A.HMIdistance(export_shape=True, export_raster=True)
453
+ B.HMIdistance()
454
+ B.hmidist
455
+
456
+ where
457
+ sources: (Geo)Pandas DataFrame with the locations of the sources from which to compute distances
458
+ should have columns with either X/Y or LAT/LON
459
+ destinations: (Geo)Pandas DataFrame with the locations of the destinations to which to compute distances
460
+ should have columns with either X/Y or LAT/LON
461
+ lat: Name of column with latitudes (if applicable)
462
+ lon: Name of column with longitudes (if applicable)
463
+ y: Name of column with cylindrical equal area projected latitudes (if applicable)
464
+ x: Name of column with cylindrical equal area projected longitudes (if applicable)
465
+ projected: Boolean, references is data is projected or not
466
+ fast: Use small cost surface or not
467
+ """
468
+ # Initialize Object
469
+ def __init__(self, sources, destinations, lat=None, lon=None, x=None, y=None, projected=None, fast=True, file='HMIOcean', clip=None):
470
+ super(HMIOcean, self).__init__(sources, destinations, lat=lat, lon=lon, x=x, y=y, projected=projected, fast=fast, file='HMIOcean', clip=clip)
471
+ pass
472
+ def HMIOceandistance(self, isolation=True, export_raster=False, export_shape=False, routes=False, path='./'):
473
+ """
474
+ Compute HMI based travel distance measured in weeks of travel from each start point to all end points.
475
+ The function returns the distances between the start point and the end points as a Pandas dataframe.
476
+ Additionally, for each start point it computes the level of isolation, i.e. its average travel distance to
477
+ all other location on land
478
+ """
479
+ self.HMIdistance(isolation=isolation, export_raster=export_raster, export_shape=export_shape, routes=routes, path=path)
480
+ self.hmioceandist = self.hmidist
481
+ pass
482
+ '''
483
+
484
+ def download():
485
+ path = os.path.dirname(os.path.realpath(__file__)) + '/data/'
486
+ urlretrieve('https://zenodo.org/records/14285746/files/HMI.tif?download=1', filename=path+'HMI.tif')
487
+ urlretrieve('https://zenodo.org/records/14285746/files/HMISea.tif?download=1', filename=path+'HMISea.tif')
488
+ urlretrieve('https://zenodo.org/records/14285746/files/HMI10.tif?download=1', filename=path+'HMI10.tif')
489
+ urlretrieve('https://zenodo.org/records/14285746/files/HMISea10.tif?download=1', filename=path+'HMISea10.tif')
490
+ pass
hmi/main.py ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """Program entry point"""
4
+
5
+ from __future__ import print_function
6
+
7
+ import argparse
8
+ import sys
9
+
10
+ import .metadata
11
+
12
+ def main(argv):
13
+ """Program entry point.
14
+
15
+ :param argv: command-line arguments
16
+ :type argv: :class:`list`
17
+ """
18
+ author_strings = []
19
+ for name, email in zip(metadata.authors, metadata.emails):
20
+ author_strings.append('Author: {0} <{1}>'.format(name, email))
21
+
22
+ epilog = '''
23
+ {project} {version}
24
+
25
+ {authors}
26
+ URL: <{url}>
27
+ '''.format(
28
+ project=metadata.project,
29
+ version=metadata.version,
30
+ authors='\n'.join(author_strings),
31
+ url=metadata.url)
32
+
33
+ arg_parser = argparse.ArgumentParser(
34
+ prog=argv[0],
35
+ formatter_class=argparse.RawDescriptionHelpFormatter,
36
+ description=metadata.description,
37
+ epilog=epilog)
38
+ arg_parser.add_argument(
39
+ '-V', '--version',
40
+ action='version',
41
+ version='{0} {1}'.format(metadata.project, metadata.version))
42
+
43
+ arg_parser.parse_args(args=argv[1:])
44
+
45
+ print(epilog)
46
+
47
+ return 0
48
+
49
+
50
+ def entry_point():
51
+ """Zero-argument entry point for use with setuptools/distribute."""
52
+ raise SystemExit(main(sys.argv))
53
+
54
+
55
+ if __name__ == '__main__':
56
+ entry_point()
hmi/metadata.py ADDED
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Project metadata
3
+
4
+ Information describing the project.
5
+ """
6
+
7
+ # The package name, which is also the "UNIX name" for the project.
8
+ package = 'hmi'
9
+ project = "Human Mobility Index"
10
+ project_no_spaces = project.replace(' ', '')
11
+ version = '0.8.2'
12
+ description = "The HMI package is a python module that provides an interface to compute distances between locations based on Özak's (2010, 2012, 2015) Human Mobility Index."
13
+ authors = ['Ömer Özak']
14
+ authors_string = ', '.join(authors)
15
+ emails = ['ozak@smu.edu']
16
+ license = 'GNU'
17
+ copyright = '(c)' + authors_string + '(2015)'
18
+ url = 'http://omerozak.com/'