-
Notifications
You must be signed in to change notification settings - Fork 69
Adding metrics to strokes made by COINS #704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…/momepy into stroke-graph
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #704 +/- ##
=======================================
- Coverage 97.4% 97.0% -0.3%
=======================================
Files 26 28 +2
Lines 4328 4303 -25
=======================================
- Hits 4214 4176 -38
- Misses 114 127 +13
🚀 New features to boost your workflow:
|
once api is fixed, ping martin, get thumbs up, then we can work on tests. |
|
Functions should be fixed, with added documentation and an example in a jupyter notebook. Tell me what you think @martinfleis (and @anastassiavybornova for the documentation) and then we can continue with the tests ! |
martinfleis
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool! This looks much better and super close to what I think is mergeable. Some comments left in the code.
| >>> gdf = momepy.remove_false_nodes(gdf) | ||
| >>> primal_graph = momepy.gdf_to_nx(gdf, preserve_index=True, approach="primal") | ||
| >>> lines = momepy.nx_to_gdf(primal_graph, points=False, lines=True) | ||
| >>> coins = momepy.COINS(lines) | ||
| >>> stroke_graph = momepy.coins_to_nx(coins) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| >>> gdf = momepy.remove_false_nodes(gdf) | |
| >>> primal_graph = momepy.gdf_to_nx(gdf, preserve_index=True, approach="primal") | |
| >>> lines = momepy.nx_to_gdf(primal_graph, points=False, lines=True) | |
| >>> coins = momepy.COINS(lines) | |
| >>> stroke_graph = momepy.coins_to_nx(coins) | |
| >>> coins = momepy.COINS(gdf) | |
| >>> stroke_graph = momepy.coins_to_nx(coins) |
This should work as well, no?
| Creates the stroke graph of a street network. The stroke graph is similar to, but | ||
| not identical with, the dual graph. In the stroke graph, each stroke (see | ||
| ``momepy.COINS``) is a node; and each intersection between two strokes is an edge. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Creates the stroke graph of a street network. The stroke graph is similar to, but | |
| not identical with, the dual graph. In the stroke graph, each stroke (see | |
| ``momepy.COINS``) is a node; and each intersection between two strokes is an edge. | |
| Creates the continuity stroke graph of a street network. The continuity stroke graph is similar to, but | |
| not identical with, the dual graph. In the continuity stroke graph, each stroke (see | |
| ``momepy.COINS``) is a node; and each intersection between two strokes is an edge. |
| Parameters | ||
| ---------- | ||
| coins: momepy.COINS | ||
| Strokes computed from a street network. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Strokes computed from a street network. | |
| Continuity strokes computed from a street network. |
| stroke_gdf["rep_point"] = stroke_gdf.geometry.apply( | ||
| lambda x: x.interpolate(0.5, normalized=True) | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| stroke_gdf["rep_point"] = stroke_gdf.geometry.apply( | |
| lambda x: x.interpolate(0.5, normalized=True) | |
| ) | |
| stroke_gdf["rep_point"] = stroke_gdf.geometry.interpolate(0.5, normalized=True) |
Vectorized ops are always preferred. There's nearly no need for apply in geopandas.
| def stroke_connectivity(stroke_graph): | ||
| """ | ||
| Computes the stroke's connectivity. Connectivity is defined as | ||
| the number of arcs a stroke intersects. Comparing to the degree, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is an "arc" in this context?
| Computing dual graph | ||
| --------------------- | ||
|
|
||
| Set of functions to create a dual graph and compute metrics on: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Computing dual graph | |
| --------------------- | |
| Set of functions to create a dual graph and compute metrics on: | |
| Computing continuity graph | |
| --------------------------- | |
| Set of functions to create a continuity graph and compute metrics on: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"It is a dual graph as defined in Porta, S., Crucitti, P. and Latora, V., 2006." - is it? That is not based on continuity strokes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from matplotlib import pyplot as plt -> import matplotlib.pyplot as plt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you go through the primal graph there? Seems wasteful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could go on but I might take a stab at revising this notebook myself later on. Generally it feels a bit too complicated. A user guide should narrowly focus whatever is being explained without other complications. there's a lot of additional code in here that is more a distraction than anything else. Like the conversion to nx graph, all those multilayer plots, stuff like stroke_betweenness_dict mapping and so on. Keep the notebook as is for now though. Might be easier for me to do that than trying to explain myself.
| """ | ||
| point = tuple(point) | ||
| coords = list(linestring.coords) | ||
| assert point in coords, "point not on linestring!" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| assert point in coords, "point not on linestring!" |
You got this covered by the more appropriate ValueError below.
| stroke_gdf["edge_indeces"] = stroke_gdf.stroke_id.apply( | ||
| lambda x: list(stroke_attribute[stroke_attribute == x].index) | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| stroke_gdf["edge_indeces"] = stroke_gdf.stroke_id.apply( | |
| lambda x: list(stroke_attribute[stroke_attribute == x].index) | |
| ) | |
| stroke_attribute.groupby(stroke_attribute).apply(lambda group: group.index.tolist()) |
No for-loops over rows :). This is much faster.
| # Add stroke ID to each edge on (primal) graph | ||
| nx.set_edge_attributes( | ||
| G=graph, | ||
| values={ | ||
| e: int(stroke_attribute[graph.edges[e]["index_position"]]) | ||
| for e in graph.edges | ||
| }, | ||
| name="stroke_id", | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get the point of this. graph already has stroke_id on edges coming from gdf_to_nx. It is a wrong one or what?
See #591 for explanation. Drafted without full tests and documentation. We ran the code with https://github.com/pysal/momepy/blob/main/ci/envs/312-latest.yaml.
Contribution
Adding a
strokegraph.pyfile with 2 main functions:strokes_to_graphtakingmomepy.COINSas an input and returning a dual graph where each node is a stroke inmomepy.COINSand each edge is when two strokes are intersecting. Optional arguments include computing additional metrics on the strokes (degree, closeness, betweenness, connectivity, access, orthogonality, and spacing, see (El Gouj et al., 2022) for details), and returning the primal graph of the street network with the information about each stroke on every edge composing them.graph_to_strokestaking the dual graph generated bystrokes_to_graphas an input and returning amomepy.COINSobject as an output.Questions
graph_to_strokes, as it returns the output frommomepy.COINS?strokes_to_graphwhere the return of the primal graph is optional make sense ?TODOs