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 +53 -0
- hmi/hmi.py +490 -0
- hmi/main.py +56 -0
- hmi/metadata.py +18 -0
- hmi-0.1.dist-info/LICENSE +674 -0
- hmi-0.1.dist-info/METADATA +100 -0
- hmi-0.1.dist-info/RECORD +9 -0
- hmi-0.1.dist-info/WHEEL +5 -0
- hmi-0.1.dist-info/top_level.txt +1 -0
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/'
|