Skip to content

Commit

Permalink
build a graph from OSM information (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoolive authored Dec 13, 2024
1 parent 8c2c6cb commit 7db32b1
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 541 deletions.
23 changes: 10 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[project]
name = "cartes"
version = "0.8.3"
version = "0.8.4"
description = "A generic toolbox for building maps in Python"
authors = [{ name = "Xavier Olive", email = "[email protected]" }]
license = "MIT"
readme = "readme.md"
requires-python = ">=3.9"
requires-python = ">=3.10"
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Information Technology",
Expand All @@ -19,14 +19,14 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Typing :: Typed",
]
dependencies = [
"altair>=5.4.1",
"appdirs>=1.4.4",
"beautifulsoup4>=4.12.3",
"cartopy>=0.23.0; python_version<'3.10'",
"cartopy>=0.24.0; python_version>='3.10'",
"cartopy>=0.24.0",
"fastapi>=0.115.0",
"geopandas>=1.0.1",
"httpx[http2]>=0.27.2",
Expand All @@ -35,13 +35,10 @@ dependencies = [
"matplotlib>=3.9.2",
"nest-asyncio>=1.6.0",
"networkx>=3.2.1",
"numpy>=2.0.2; python_version<'3.10'",
"numpy>=2.1.2; python_version>='3.10'",
"numpy>=2.1.2",
"pandas>=2.2.3",
"pyproj<3.7.1; python_version<'3.10'",
"pyproj>=3.7.0; python_version>='3.10'",
"scipy>=1.13.1; python_version<'3.10'",
"scipy>=1.14.1; python_version>='3.10'",
"pyproj>=3.7.0",
"scipy>=1.14.1",
"shapely>=2.0.6",
"tqdm>=4.66.5",
"uvicorn>=0.31.0",
Expand Down Expand Up @@ -86,7 +83,7 @@ lint.select = [
"RUF",
]
line-length = 80
target-version = "py39"
target-version = "py310"

[tool.ruff.lint.isort]
known-first-party = ["numpy", "pandas", "pyproj", "shapely"]
Expand All @@ -95,9 +92,9 @@ known-first-party = ["numpy", "pandas", "pyproj", "shapely"]
# Specify the target platform details in config, so your developers are
# free to run mypy on Windows, Linux, or macOS and get consistent
# results.
python_version = 3.9
python_version = "3.10"

exclude = ["_ignore"]
exclude = ["_ignore", "docs/build/"]

check_untyped_defs = true
follow_imports = "normal"
Expand Down
6 changes: 3 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ More in the [documentation](https://cartes-viz.github.io/gallery.html)

Latest release:

Recommended, with conda:
Recommended for beginners, with conda:

```sh
conda install -c conda-forge cartes
Expand All @@ -47,12 +47,12 @@ or with pip:
pip install cartes
```

Development version:
Development version, with uv:

```sh
git clone https://github.com/xoolive/cartes
cd cartes
pip install .
uv sync --dev
```

## Documentation
Expand Down
8 changes: 4 additions & 4 deletions src/cartes/atlas/topo.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,11 @@ def _repr_html_(self) -> str:
return self.df._repr_html_()

@property
def url(self):
def url(self) -> str:
return self.df.iloc[0].download_url

@property
def path(self):
def path(self) -> Path:
assert self.api is not None
return Path(self.api.id_) / self.df.iloc[0].path

Expand All @@ -210,12 +210,12 @@ def features(self) -> list[str]:
return list(get_json(self)["objects"].keys())

@property
def topo_feature(self):
def topo_feature(self) -> alt.UrlData:
feature = self.features[0]
return alt.topo_feature(self.url, feature=feature)

@property
def data(self):
def data(self) -> gpd.GeoDataFrame:
feature = self.features[0]
return gpd.read_file(
StringIO(json.dumps(get_json(self))), layout=feature
Expand Down
37 changes: 35 additions & 2 deletions src/cartes/crs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from typing import ClassVar, TypedDict

# All projections first come from the regular Cartopy CRS module
from cartopy.crs import ( # noqa: F401
from cartopy.crs import (
Globe,
InterruptedGoodeHomolosine,
LambertCylindrical,
Expand All @@ -39,7 +39,7 @@
from .info import valid_crs # noqa: F401

# These ones can be unpacked in altair!
from .vega_params import ( # noqa: F401
from .vega_params import (
OSGB,
OSNI,
UTM,
Expand All @@ -61,6 +61,39 @@

# fmt: on

__all__ = [
"AlbersEqualArea",
"AlbersUSA",
"AzimuthalEquidistant",
"EqualEarth",
"EquidistantConic",
"EuroPP",
"GaussKrueger",
"GaussKruger",
"Globe",
"Gnomonic",
"Hartebeesthoek94",
"InterruptedGoodeHomolosine",
"LambertAzimuthalEqualArea",
"LambertConformal",
"LambertCylindrical",
"Mercator",
"Miller",
"Mollweide",
"Orthographic",
"OSGB",
"OSNI",
"PlateCarree",
"Projection",
"RDN2008",
"Robinson",
"RotatedPole",
"Sinusoidal",
"Stereographic",
"TransverseMercator",
"UTM",
]

# Silence a fiona warning
warnings.simplefilter(action="ignore", category=UserWarning)

Expand Down
14 changes: 11 additions & 3 deletions src/cartes/crs/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@

class EPSGProjectionMeta(ABCMeta):
@staticmethod
def init_args(parameters: List[Dict[str, Any]], proj: Projection):
def init_args(
parameters: List[Dict[str, Any]],
proj: Projection,
) -> Dict[str, Any]:
all_ = dict(
(arg_codes[elt["id"]["code"]], elt["value"]) for elt in parameters
)
Expand All @@ -105,7 +108,10 @@ def init_args(parameters: List[Dict[str, Any]], proj: Projection):
return all_

@staticmethod
def projected_shape(bbox: Dict[str, float], transformer):
def projected_shape(
bbox: Dict[str, float],
transformer,
) -> geometry.LineString:
y0 = bbox["south_latitude"]
x0 = bbox["east_longitude"]
y1 = bbox["north_latitude"]
Expand All @@ -131,7 +137,9 @@ def __new__(metacls, name, bases, attr_dict):
crs = CRS(identifier)
basic_dict = crs.to_dict()
_log.debug(pprint.pformat(basic_dict))
base_class = base_classes.get(basic_dict["proj"], Projection)
base_class: Projection = base_classes.get(
basic_dict["proj"], Projection
)
_log.debug(f"Generate a class based on {base_class.__name__}")

transformer = Transformer.from_proj(
Expand Down
34 changes: 32 additions & 2 deletions src/cartes/osm/overpass/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import logging
from concurrent.futures import Future, ProcessPoolExecutor, as_completed
from functools import lru_cache
Expand All @@ -14,6 +16,7 @@
)

import geopandas as gpd
import networkx as nx
from tqdm import tqdm

import pandas as pd
Expand Down Expand Up @@ -237,8 +240,6 @@ def length(self) -> "Overpass":
)

def coloring(self) -> "Overpass":
import networkx as nx

self.graph = nx.Graph()
for elt in self:
for neighbour in elt.neighbours:
Expand All @@ -250,6 +251,35 @@ def coloring(self) -> "Overpass":
merge.graph = self.graph
return merge

def network_graph(
self, *args: str, query_str: None | str = None
) -> nx.Graph:
"""Nice to have at least "geometry" as one of the args."""
graph = nx.Graph()
positions = {}
df = self.parse().query('type_ == "way"')
if query_str is not None:
df = df.query(query_str)

for _, e in df.iterrows():
if e.geometry.geom_type == "LineString":
first, *_, last = e.nodes
first_pos, *_, last_pos = e["geometry"].coords
positions[first] = first_pos
positions[last] = last_pos

# add any extra information to the edges
kwargs = dict((arg, e.get(arg, None)) for arg in args)
kwargs["first"] = first
kwargs["last"] = last
graph.add_edge(first, last, **kwargs)

# it is more comfortable to have the positions attached to nodes
graph.nodes[first]["pos"] = first_pos
graph.nodes[last]["pos"] = last_pos

return graph

def simplify(
self,
resolution: Optional[float] = None,
Expand Down
Loading

0 comments on commit 7db32b1

Please sign in to comment.