Skip to content

Commit

Permalink
Merge pull request #123 from jGaboardi/add_snap_dist
Browse files Browse the repository at this point in the history
[WIP] Add snap dist
  • Loading branch information
jGaboardi committed Sep 22, 2018
2 parents 529b918 + 43db978 commit b4a9490
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 38 deletions.
10 changes: 5 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ matrix:
env: PYSAL_PYPI=false PYSAL_PLUS=true

before_install:
- wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh
- wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh
- chmod +x miniconda.sh
- ./miniconda.sh -b -p ./miniconda
- export PATH=`pwd`/miniconda/bin:$PATH
- conda update --yes conda
- conda config --add channels conda-forge
- conda config --append channels conda-forge
- conda create -y -q -n test-env python=$TRAVIS_PYTHON_VERSION
- source activate test-env

install:
- conda install --yes pip
- conda install --yes -c conda-forge geopandas
- conda install --yes geopandas
- if "$PYSAL_PYPI"; then
echo 'testing pypi libpysal' && pip install libpysal;
else echo 'testing git libpysal';
Expand All @@ -47,11 +47,11 @@ install:
echo 'plus testing'; conda install --yes --file requirements_plus.txt;
else conda install --yes --file requirements.txt;
fi;
- pip install -r requirements_dev.txt
- pip install -r requirements_dev.txt -r requirements_docs.txt

script:
- python setup.py sdist >/dev/null
- nosetests --with-coverage --cover-package=spaghetti;
- nosetests --with-doctest --with-coverage --cover-package=spaghetti;

notifications:
email:
Expand Down
3 changes: 2 additions & 1 deletion requirements_docs.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
sphinx>=1.4.3
sphinxcontrib-bibtex
sphinx_bootstrap_theme
numpydoc
numpydoc
esda
81 changes: 67 additions & 14 deletions spaghetti/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,28 @@ def compute_distance_to_nodes(self, x, y, edge):
return d1, d2


def compute_snap_dist(self, pattern, idx):
"""Given an observation snapped to a network edge, calculate the
distance from the original location to the snapped location.
Parameters
-----------
pattern : spaghetti.network.PointPattern
point pattern object
idx : int
point id
Return
------
dist : float
euclidean distance from original location to snapped location.
"""
loc = pattern.points[idx]['coordinates']
snp = pattern.snapped_coordinates[idx]
dist = util.compute_length(loc, snp)
return dist

def _snap_to_edge(self, pointpattern):
""" Used internally to snap point observations to network edges.
Expand All @@ -569,6 +591,8 @@ def _snap_to_edge(self, pointpattern):

obs_to_edge = {}
dist_to_node = {}
dist_snapped = {}
obs_to_node = defaultdict(list)

pointpattern.snapped_coordinates = {}
segments = []
Expand All @@ -578,13 +602,15 @@ def _snap_to_edge(self, pointpattern):
tail = self.node_coords[edge[1]]
segments.append(cg.Chain([head, tail]))
s2e[(head, tail)] = edge


# snap points
points = {}
p2id = {}
for pointIdx, point in pointpattern.points.items():
points[pointIdx] = point['coordinates']
snapped = util.snap_points_on_segments(points, segments)

# record obs_to_edge, dist_to_node, and dist_snapped
for pointIdx, snapInfo in snapped.items():
x, y = snapInfo[1].tolist()
edge = s2e[tuple(snapInfo[0])]
Expand All @@ -594,15 +620,19 @@ def _snap_to_edge(self, pointpattern):
pointpattern.snapped_coordinates[pointIdx] = (x, y)
d1, d2 = self.compute_distance_to_nodes(x, y, edge)
dist_to_node[pointIdx] = {edge[0]: d1, edge[1]: d2}

dist_snapped[pointIdx] = self.compute_snap_dist(pointpattern,
pointIdx)

# record obs_to_node
obs_to_node = defaultdict(list)
for k, v in obs_to_edge.items():
keys = v.keys()
obs_to_node[k[0]] = keys
obs_to_node[k[1]] = keys

pointpattern.obs_to_edge = obs_to_edge
pointpattern.dist_to_node = dist_to_node
pointpattern.dist_snapped = dist_snapped
pointpattern.obs_to_node = list(obs_to_node)


Expand Down Expand Up @@ -635,8 +665,9 @@ def count_per_edge(self, obs_on_network, graph=True):
>>> counts = ntw.count_per_edge(ntw.pointpatterns['crimes']
... .obs_to_edge, graph=False)
>>> list(counts.values())[:3]
[1, 3, 2]
>>> counts[(140, 142)]
10
>>> s = sum([v for v in list(counts.values())])
>>> s
287
Expand Down Expand Up @@ -847,7 +878,7 @@ def node_distance_matrix(self, n_processes, gen_tree=False):

def allneighbordistances(self, sourcepattern, destpattern=None,
fill_diagonal=None, n_processes=None,
gen_tree=False):
gen_tree=False, snap_dist=False):
""" Compute either all distances between i and j in a single point
pattern or all distances between each i from a source pattern and all
j from a destination pattern.
Expand Down Expand Up @@ -876,6 +907,10 @@ def allneighbordistances(self, sourcepattern, destpattern=None,
gen_tree : bool
rebuild shortest path {True}, or skip {False}
snap_dist : bool
include the distance from the original location to the snapped
location along the network. Default is False.
Returns
-------
Expand Down Expand Up @@ -927,11 +962,11 @@ def allneighbordistances(self, sourcepattern, destpattern=None,

# Source setup
src_indices = list(sourcepattern.points.keys())
src_d2n = copy.deepcopy(sourcepattern.dist_to_node)
nsource_pts = len(src_indices)
src_dist_to_node = sourcepattern.dist_to_node
src_nodes = {}
for s in src_indices:
e1, e2 = src_dist_to_node[s].keys()
e1, e2 = src_d2n[s].keys()
src_nodes[s] = (e1, e2)

# Destination setup
Expand All @@ -940,12 +975,22 @@ def allneighbordistances(self, sourcepattern, destpattern=None,
symmetric = True
destpattern = sourcepattern
dest_indices = list(destpattern.points.keys())
dst_d2n = copy.deepcopy(destpattern.dist_to_node)
ndest_pts = len(dest_indices)
dest_dist_to_node = destpattern.dist_to_node
dest_searchpts = copy.deepcopy(dest_indices)
dest_nodes = {}

# add snapping distance to each pointpattern
if snap_dist:
patterns = [sourcepattern, destpattern]
dist_copies = [src_d2n, dst_d2n]
for elm, pp in enumerate(patterns):
for pidx, dists_dict in dist_copies[elm].items():
for nidx, ndist in dists_dict.items():
dists_dict[nidx] = ndist + pp.dist_snapped[pidx]

for s in dest_indices:
e1, e2 = dest_dist_to_node[s].keys()
e1, e2 = dst_d2n[s].keys()
dest_nodes[s] = (e1, e2)

# Output setup
Expand All @@ -958,7 +1003,7 @@ def allneighbordistances(self, sourcepattern, destpattern=None,
source1, source2 = src_nodes[p1]
set1 = set(src_nodes[p1])
# Distance from node1 to p, distance from node2 to p.
sdist1, sdist2 = src_dist_to_node[p1].values()
sdist1, sdist2 = src_d2n[p1].values()

if symmetric:
# Only compute the upper triangle if symmetric.
Expand All @@ -973,7 +1018,8 @@ def allneighbordistances(self, sourcepattern, destpattern=None,
nearest[p1, p2] = computed_length

else:
ddist1, ddist2 = dest_dist_to_node[p2].values()
ddist1, ddist2 = dst_d2n[p2].values()

d11 = self.distancematrix[source1][dest1]
d21 = self.distancematrix[source2][dest1]
d12 = self.distancematrix[source1][dest2]
Expand All @@ -988,6 +1034,7 @@ def allneighbordistances(self, sourcepattern, destpattern=None,
if sd_1 > sd_21:
sd_1 = sd_21
sp_combo1 = source2, dest1

# Now add the point to node one distance on
# the destination edge.
len_1 = sd_1 + ddist1
Expand Down Expand Up @@ -1033,7 +1080,8 @@ def allneighbordistances(self, sourcepattern, destpattern=None,

def nearestneighbordistances(self, sourcepattern, destpattern=None,
n_processes=None, gen_tree=False,
all_dists=None, keep_zero_dist=True):
all_dists=None, snap_dist=False,
keep_zero_dist=True):
"""Compute the interpattern nearest neighbor distances or the
intrapattern nearest neighbor distances between a source
pattern and a destination pattern.
Expand All @@ -1058,6 +1106,10 @@ def nearestneighbordistances(self, sourcepattern, destpattern=None,
all_dists : numpy.ndarray
An array of shape (n,n) storing distances between all points.
snap_dist : bool
include the distance from the original location to the snapped
location along the network. Default is False.
keep_zero_dist : bool
Include zero values in minimum distance (True) or exclude (False).
Default is True. If the source pattern is the same as the
Expand Down Expand Up @@ -1112,7 +1164,8 @@ def nearestneighbordistances(self, sourcepattern, destpattern=None,
destpattern=destpattern,
fill_diagonal=fill_diagonal,
n_processes=n_processes,
gen_tree=gen_tree)
gen_tree=gen_tree,
snap_dist=snap_dist)
nearest = {}

for source_index in sourcepattern.points.keys():
Expand Down
20 changes: 16 additions & 4 deletions spaghetti/tests/test_network_api_from_gdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ def test_all_neighbor_distances(self):
observed = matrix2.diagonal()
known = np.zeros(matrix2.shape[0])
self.assertEqual(observed.all(), known.all())

matrix3 = self.ntw.allneighbordistances('schools', snap_dist=True)
known_mtx_val = 3218.2597894
observed_mtx_val = matrix3
self.assertAlmostEqual(observed_mtx_val[0, 1], known_mtx_val, places=4)

def test_nearest_neighbor_distances(self):
# general test
Expand All @@ -132,16 +137,23 @@ def test_nearest_neighbor_distances(self):
np.testing.assert_array_almost_equal_nulp(nndv1, nndv2)

# nearest neighbor keeping zero test
known_zero = ([19], 0.0)
known_zero = ([19], 0.0)[0]
nn_c = self.ntw.nearestneighbordistances('crimes',
keep_zero_dist=True)
self.assertEqual(nn_c[18], known_zero)
self.assertEqual(nn_c[18][0], known_zero)

# nearest neighbor omitting zero test
known_nonzero = ([11], 165.33982412719126)
known_nonzero = ([11], 165.33982412719126)[1]
nn_c = self.ntw.nearestneighbordistances('crimes',
keep_zero_dist=False)
self.assertEqual(nn_c[18], known_nonzero)
self.assertAlmostEqual(nn_c[18][1], known_nonzero, places=4)

# nearest neighbor with snap distance
known_neigh = ([3], 402.5219673922477)[1]
nn_c = self.ntw.nearestneighbordistances('crimes',
keep_zero_dist=True,
snap_dist=True)
self.assertAlmostEqual(nn_c[0][1], known_neigh, places=4)

@unittest.skipIf(GEOPANDAS_EXTINCT, 'Missing Geopandas')
class TestNetworkAnalysis(unittest.TestCase):
Expand Down
20 changes: 16 additions & 4 deletions spaghetti/tests/test_network_api_from_shp.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ def test_all_neighbor_distances(self):
observed = matrix2.diagonal()
known = np.zeros(matrix2.shape[0])
self.assertEqual(observed.all(), known.all())

matrix3 = self.ntw.allneighbordistances('schools', snap_dist=True)
known_mtx_val = 3218.2597894
observed_mtx_val = matrix3
self.assertAlmostEqual(observed_mtx_val[0, 1], known_mtx_val, places=4)

def test_nearest_neighbor_distances(self):
# general test
Expand All @@ -121,16 +126,23 @@ def test_nearest_neighbor_distances(self):
np.testing.assert_array_almost_equal_nulp(nndv1, nndv2)

# nearest neighbor keeping zero test
known_zero = ([19], 0.0)
known_zero = ([19], 0.0)[0]
nn_c = self.ntw.nearestneighbordistances('crimes',
keep_zero_dist=True)
self.assertEqual(nn_c[18], known_zero)
self.assertEqual(nn_c[18][0], known_zero)

# nearest neighbor omitting zero test
known_nonzero = ([11], 165.33982412719126)
known_nonzero = ([11], 165.33982412719126)[1]
nn_c = self.ntw.nearestneighbordistances('crimes',
keep_zero_dist=False)
self.assertEqual(nn_c[18], known_nonzero)
self.assertAlmostEqual(nn_c[18][1], known_nonzero, places=4)

# nearest neighbor with snap distance
known_neigh = ([3], 402.5219673922477)[1]
nn_c = self.ntw.nearestneighbordistances('crimes',
keep_zero_dist=True,
snap_dist=True)
self.assertAlmostEqual(nn_c[0][1], known_neigh, places=4)

class TestNetworkAnalysis(unittest.TestCase):

Expand Down
20 changes: 16 additions & 4 deletions spaghetti/tests/test_network_from_gdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ def test_all_neighbor_distances(self):
observed = matrix2.diagonal()
known = np.zeros(matrix2.shape[0])
self.assertEqual(observed.all(), known.all())

matrix3 = self.ntw.allneighbordistances('schools', snap_dist=True)
known_mtx_val = 3218.2597894
observed_mtx_val = matrix3
self.assertAlmostEqual(observed_mtx_val[0, 1], known_mtx_val, places=4)

def test_nearest_neighbor_distances(self):
# general test
Expand All @@ -134,16 +139,23 @@ def test_nearest_neighbor_distances(self):
np.testing.assert_array_almost_equal_nulp(nndv1, nndv2)

# nearest neighbor keeping zero test
known_zero = ([19], 0.0)
known_zero = ([19], 0.0)[0]
nn_c = self.ntw.nearestneighbordistances('crimes',
keep_zero_dist=True)
self.assertEqual(nn_c[18], known_zero)
self.assertEqual(nn_c[18][0], known_zero)

# nearest neighbor omitting zero test
known_nonzero = ([11], 165.33982412719126)
known_nonzero = ([11], 165.33982412719126)[1]
nn_c = self.ntw.nearestneighbordistances('crimes',
keep_zero_dist=False)
self.assertEqual(nn_c[18], known_nonzero)
self.assertAlmostEqual(nn_c[18][1], known_nonzero, places=4)

# nearest neighbor with snap distance
known_neigh = ([3], 402.5219673922477)[1]
nn_c = self.ntw.nearestneighbordistances('crimes',
keep_zero_dist=True,
snap_dist=True)
self.assertAlmostEqual(nn_c[0][1], known_neigh, places=4)

@unittest.skipIf(GEOPANDAS_EXTINCT, 'Missing Geopandas')
class TestNetworkAnalysis(unittest.TestCase):
Expand Down

0 comments on commit b4a9490

Please sign in to comment.