Skip to content

Commit

Permalink
Merge pull request #179 from NREL/ndr/misc-updates
Browse files Browse the repository at this point in the history
update file io; add path to dataframe method
  • Loading branch information
nreinicke committed Aug 12, 2023
2 parents d97d475 + af98183 commit 7b97bda
Show file tree
Hide file tree
Showing 12 changed files with 12,135 additions and 51 deletions.
6 changes: 6 additions & 0 deletions docs/source/general/contributing.rst
Expand Up @@ -181,6 +181,12 @@ To preview the documentation locally:
Maintainer Information
----------------------------------------

Adding a new package dependency
________________________________________

To add a new package dependency, add it to the ``dependency`` list in the ``/pyproject.toml`` file.


Updating Version Locations
________________________________________

Expand Down
25 changes: 1 addition & 24 deletions docs/source/general/examples.rst
Expand Up @@ -4,27 +4,4 @@ Examples
Example Usage
-------------

Currently, ``osmnx`` is used to download a road network and match it using the ``LCSSMatcher``.

The ``LCSSMatcher`` implements the map matching algorithm described in `this <https://journals.sagepub.com/doi/10.3141/2645-08/>`_ paper.

.. code-block:: python
from mappymatch import root
from mappymatch.matchers.lcss.lcss import LCSSMatcher
from mappymatch.utils.geo import geofence_from_trace
from mappymatch.maps.nx.readers.osm_readers import read_osm_nxmap
from mappymatch.constructs.trace import Trace
trace = Trace.from_csv(root() / "resources/traces/sample_trace_1.csv")
# generate a geofence polygon that surrounds the trace; units are in meters;
# this is used to query OSM for a small map that we can match to
geofence = geofence_from_trace(trace, padding=1e3)
# uses osmnx to pull a networkx map from the OSM database
road_map = read_osm_nxmap(geofence)
matcher = LCSSMatcher(road_map)
matches = matcher.match_trace(trace)
Checkout `this notebook <https://github.com/NREL/mappymatch-examples/blob/main/lcss-example.ipynb>`_ for a detailed example of using the LCSSMatcher.
4 changes: 1 addition & 3 deletions docs/source/general/quickstart.rst
@@ -1,5 +1,3 @@
Quickstart
==============
Checkout the :ref:`mainindex` page for an overview.

Quickstart text.
Checkout `this notebook <https://github.com/NREL/mappymatch-examples/blob/main/lcss-example.ipynb>`_ for a detailed example of using the LCSSMatcher.
2 changes: 1 addition & 1 deletion mappymatch/constructs/geofence.py
Expand Up @@ -57,7 +57,7 @@ def from_geojson(cls, file: Union[Path, str]) -> Geofence:
def from_trace(
cls,
trace: Trace,
padding: float = 15,
padding: float = 1e3,
crs: CRS = LATLON_CRS,
buffer_res: int = 2,
) -> Geofence:
Expand Down
2 changes: 1 addition & 1 deletion mappymatch/constructs/road.py
Expand Up @@ -49,7 +49,7 @@ def to_dict(self) -> Dict[str, Any]:
"""
d = self._asdict()
d["origin_junction_id"] = self.road_id.start
d["origin_destination_id"] = self.road_id.end
d["destination_junction_id"] = self.road_id.end
d["road_key"] = self.road_id.key

return d
Expand Down
25 changes: 14 additions & 11 deletions mappymatch/maps/nx/nx_map.py
Expand Up @@ -199,13 +199,14 @@ def from_file(cls, file: Union[str, Path]) -> NxMap:
"""
p = Path(file)
if p.suffix == ".pickle":
g = nx.readwrite.read_gpickle(file)
return NxMap(g)
raise ValueError(
"NxMap does not support reading from pickle files, please use .json instead"
)
elif p.suffix == ".json":
with p.open("r") as f:
return NxMap.from_dict(json.load(f))
else:
raise TypeError("NxMap only supports pickle and json files")
raise TypeError("NxMap only supports reading from json files")

@classmethod
def from_geofence(
Expand Down Expand Up @@ -246,24 +247,26 @@ def to_file(self, outfile: Union[str, Path]):
outfile = Path(outfile)

if outfile.suffix == ".pickle":
nx.write_gpickle(self.g, str(outfile))
raise ValueError(
"NxMap does not support writing to pickle files, please use .json instead"
)
elif outfile.suffix == ".json":
graph_dict = self.to_dict()
with open(outfile, "w") as f:
json.dump(graph_dict, f)
else:
raise TypeError(
"NxMap only supports writing to pickle and json files"
)
raise TypeError("NxMap only supports writing to json files")

@classmethod
def from_dict(cls, d: Dict[str, Any]) -> NxMap:
"""
Build a NxMap instance from a dictionary
"""
geom_key = d["graph"].get("geometry_key", DEFAULT_GEOMETRY_KEY)

for link in d["links"]:
geom_wkt = link["geom"]
link["geom"] = wkt.loads(geom_wkt)
geom_wkt = link[geom_key]
link[geom_key] = wkt.loads(geom_wkt)

crs_key = d["graph"].get("crs_key", DEFAULT_CRS_KEY)
crs = CRS.from_wkt(d["graph"][crs_key])
Expand All @@ -281,8 +284,8 @@ def to_dict(self) -> Dict[str, Any]:

# convert geometries to well know text
for link in graph_dict["links"]:
geom = link["geom"]
link["geom"] = geom.wkt
geom = link[self._geom_key]
link[self._geom_key] = geom.wkt

# convert crs to well known text
crs_key = graph_dict["graph"].get("crs_key", DEFAULT_CRS_KEY)
Expand Down
16 changes: 16 additions & 0 deletions mappymatch/matchers/match_result.py
Expand Up @@ -24,3 +24,19 @@ def matches_to_dataframe(self) -> pd.DataFrame:
df = df.fillna(np.NAN)

return df

def path_to_dataframe(self) -> pd.DataFrame:
"""
Returns a dataframe with the resulting estimated trace path through the road network.
The dataframe is empty if there was no path.
Returns:
A pandas dataframe
"""
if self.path is None:
return pd.DataFrame()

df = pd.DataFrame([r.to_flat_dict() for r in self.path])
df = df.fillna(np.NAN)

return df
25 changes: 17 additions & 8 deletions mappymatch/utils/plot.py
@@ -1,4 +1,4 @@
from typing import List
from typing import List, Optional, Union

import folium
import geopandas as gpd
Expand All @@ -7,14 +7,16 @@
from pyproj import CRS
from shapely.geometry import Point

from mappymatch.constructs.geofence import Geofence
from mappymatch.constructs.match import Match
from mappymatch.constructs.road import Road
from mappymatch.constructs.trace import Trace
from mappymatch.maps.nx.nx_map import NxMap
from mappymatch.matchers.matcher_interface import MatchResult
from mappymatch.utils.crs import LATLON_CRS, XY_CRS


def plot_geofence(geofence, m=None):
def plot_geofence(geofence: Geofence, m: Optional[folium.Map] = None):
"""
Plot geofence.
Expand All @@ -39,7 +41,12 @@ def plot_geofence(geofence, m=None):
return m


def plot_trace(trace, m=None, point_color="yellow", line_color=None):
def plot_trace(
trace: Trace,
m: Optional[folium.Map] = None,
point_color: str = "black",
line_color: Optional[str] = "green",
):
"""
Plot a trace.
Expand Down Expand Up @@ -79,17 +86,19 @@ def plot_trace(trace, m=None, point_color="yellow", line_color=None):
return m


def plot_matches(matches: List[Match], crs=XY_CRS):
def plot_matches(matches: Union[MatchResult, List[Match]], crs=XY_CRS):
"""
Plots a trace and the relevant matches on a folium map.
Args:
matches: The matches.
road_map: The road map.
matches: A list of matches or a MatchResult.
crs: what crs to plot in. Defaults to XY_CRS.
Returns:
A folium map with trace and matches plotted.
"""
if isinstance(matches, MatchResult):
matches = matches.matches

def _match_to_road(m):
"""Private function."""
Expand Down Expand Up @@ -142,7 +151,7 @@ def _match_to_coord(m):
return fmap


def plot_map(tmap: NxMap, m=None):
def plot_map(tmap: NxMap, m: Optional[folium.Map] = None):
"""
Plot the roads on an NxMap.
Expand Down Expand Up @@ -203,7 +212,7 @@ def plot_match_distances(matches: MatchResult):
def plot_path(
path: List[Road],
crs: CRS,
m=None,
m: Optional[folium.Map] = None,
line_color="red",
line_weight=10,
line_opacity=0.8,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Expand Up @@ -28,7 +28,7 @@ dependencies = [
"numpy",
"matplotlib",
"osmnx",
"networkx<3",
"networkx",
"igraph",
"folium",
"requests",
Expand Down

0 comments on commit 7b97bda

Please sign in to comment.