iduedu 0.1.0__tar.gz

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.
iduedu-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.1
2
+ Name: iduedu
3
+ Version: 0.1.0
4
+ Summary: IduEdu is a Python package for the creation and manipulation of complex city networks from OpenStreetMap.
5
+ License: BSD-3-Clause
6
+ Author: Danila
7
+ Author-email: 63115678+DDonnyy@users.noreply.github.com
8
+ Requires-Python: >=3.10,<3.13
9
+ Classifier: License :: OSI Approved :: BSD License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Dist: geopandas (>=0.14.4,<0.15.0)
15
+ Requires-Dist: loguru (>=0.7.2,<0.8.0)
16
+ Requires-Dist: networkit (>=11.0,<12.0)
17
+ Requires-Dist: networkx (>=3.3,<4.0)
18
+ Requires-Dist: numpy (>=1.23.5,<2.0.0)
19
+ Requires-Dist: osm2geojson (>=0.2.4,<0.3.0)
20
+ Requires-Dist: osmnx (>=1.9.4,<2.0.0)
21
+ Requires-Dist: pandas (>=2.2.0,<3.0.0)
22
+ Requires-Dist: pydantic (>=2.6.1,<3.0.0)
23
+ Requires-Dist: scipy (>=1.13.1,<2.0.0)
24
+ Requires-Dist: tqdm (>=4.66.2,<5.0.0)
25
+ Description-Content-Type: text/markdown
26
+
27
+ # IduEdu
28
+
29
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
30
+
31
+ ### IduEdu is an open-source Python library for the creation and manipulation of complex city networks from OpenStreetMap.
32
+
33
+ ## Features and how to use
34
+
35
+ 1. **[Graphs from OSM/Polygon/Name](./examples/get_any_graph.ipynb)** - Functions for building a graph of roads,
36
+ pedestrians and public transport based on OpenStreetMap (OSM), as well as creating an intermodal (public transport +
37
+ pedestrians) graph.
38
+ 2. **[Adjacency matrix](./examples/calc_adj_matrix.ipynb)** - Calculate adjacency matrix based on the provided graph and
39
+ edge weight type (_time_min_ or _length_meter_).
40
+
41
+ ## Installation
42
+
43
+ **IduEdu** can be installed with ``pip``:
44
+
45
+ ```
46
+ pip install IduEdu
47
+ ```
48
+
49
+ ### Configuration changes
50
+
51
+ ```python
52
+ from iduedu import config
53
+
54
+ config.set_timeout(10) # Timeout for overpass queries
55
+ config.change_logger_lvl('INFO') # To mute all debug msgs
56
+ config.set_enable_tqdm(False) # To mute all tqdm's progress bars
57
+ config.set_overpass_url('http://your.overpass-api.de/interpreter/URL')
58
+ ```
59
+
iduedu-0.1.0/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # IduEdu
2
+
3
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
4
+
5
+ ### IduEdu is an open-source Python library for the creation and manipulation of complex city networks from OpenStreetMap.
6
+
7
+ ## Features and how to use
8
+
9
+ 1. **[Graphs from OSM/Polygon/Name](./examples/get_any_graph.ipynb)** - Functions for building a graph of roads,
10
+ pedestrians and public transport based on OpenStreetMap (OSM), as well as creating an intermodal (public transport +
11
+ pedestrians) graph.
12
+ 2. **[Adjacency matrix](./examples/calc_adj_matrix.ipynb)** - Calculate adjacency matrix based on the provided graph and
13
+ edge weight type (_time_min_ or _length_meter_).
14
+
15
+ ## Installation
16
+
17
+ **IduEdu** can be installed with ``pip``:
18
+
19
+ ```
20
+ pip install IduEdu
21
+ ```
22
+
23
+ ### Configuration changes
24
+
25
+ ```python
26
+ from iduedu import config
27
+
28
+ config.set_timeout(10) # Timeout for overpass queries
29
+ config.change_logger_lvl('INFO') # To mute all debug msgs
30
+ config.set_enable_tqdm(False) # To mute all tqdm's progress bars
31
+ config.set_overpass_url('http://your.overpass-api.de/interpreter/URL')
32
+ ```
@@ -0,0 +1,62 @@
1
+ [tool.poetry]
2
+ name = "iduedu"
3
+ version = "0.1.0"
4
+ license = "BSD-3-Clause"
5
+ description = "IduEdu is a Python package for the creation and manipulation of complex city networks from OpenStreetMap."
6
+ authors = ["Danila <63115678+DDonnyy@users.noreply.github.com>"]
7
+ readme = "README.md"
8
+
9
+ packages = [{ include = "iduedu", from = "src" }]
10
+
11
+ [tool.poetry.dependencies]
12
+ python = ">=3.10,<3.13"
13
+ osmnx = "^1.9.4"
14
+ tqdm = "^4.66.2"
15
+ osm2geojson = "^0.2.4"
16
+ pydantic = "^2.6.1"
17
+ networkit = "^11.0"
18
+ numpy = "^1.23.5"
19
+ pandas = "^2.2.0"
20
+ geopandas = "^0.14.4"
21
+ networkx = "^3.3"
22
+ loguru = "^0.7.2"
23
+ scipy = "^1.13.1"
24
+
25
+ [tool.poetry.group.dev.dependencies]
26
+ black = "^24.2.0"
27
+ pylint = "^3.0.3"
28
+ isort = "^5.13.2"
29
+ jupyter = "^1.0.0"
30
+ ortools = "^9.9.3963"
31
+
32
+ [build-system]
33
+ requires = ["poetry-core"]
34
+ build-backend = "poetry.core.masonry.api"
35
+
36
+ [tool.black]
37
+ line-length = 120
38
+ target-version = ['py310']
39
+
40
+ [tool.pylint.format]
41
+ max-line-length = 120
42
+ expected-line-ending-format = "LF"
43
+ max-locals = 20
44
+ extension-pkg-allow-list = ["networkit"]
45
+ disable = [
46
+ "duplicate-code",
47
+ "missing-module-docstring",
48
+ "missing-function-docstring",
49
+ "too-many-locals",
50
+ "too-many-branches",
51
+ "too-many-statements",
52
+ "too-many-arguments"
53
+ ]
54
+
55
+ [tool.isort]
56
+ multi_line_output = 3
57
+ include_trailing_comma = true
58
+ force_grid_wrap = 0
59
+ use_parentheses = true
60
+ ensure_newline_before_comments = true
61
+ line_length = 120
62
+ split_on_trailing_comma = true
@@ -0,0 +1,11 @@
1
+ """
2
+ IduEdu
3
+ ========
4
+
5
+ IduEdu is a Python package for the creation and manipulation of complex city networks from OpenStreetMap.
6
+
7
+ Homepage https://github.com/DDonnyy/IduEdu.
8
+ """
9
+ from ._config import config
10
+ from ._api import *
11
+ from ._version import VERSION as __version__
@@ -0,0 +1,7 @@
1
+ # pylint: disable=unused-import
2
+ from .modules.downloaders import get_boundary
3
+ from .modules.drive_walk_builder import get_drive_graph, get_walk_graph
4
+ from .modules.intermodal_builder import get_intermodal_graph
5
+ from .modules.matrix_builder import get_adj_matrix_gdf_to_gdf
6
+ from .modules.pt_walk_joiner import join_pt_walk_graph
7
+ from .modules.public_transport_builder import get_all_public_transport_graph, get_single_public_transport_graph
@@ -0,0 +1,58 @@
1
+ import sys
2
+ from typing import Literal
3
+
4
+ from loguru import logger
5
+
6
+
7
+ class Config:
8
+ """
9
+ A configuration class to manage global settings for the application, such as Overpass API URL, timeouts, and logging options.
10
+
11
+ Attributes
12
+ ----------
13
+ overpass_url : str
14
+ URL for accessing the Overpass API. Defaults to "http://lz4.overpass-api.de/api/interpreter".
15
+ timeout : int or None
16
+ Timeout in seconds for API requests. If None, no timeout is applied.
17
+ enable_tqdm_bar : bool
18
+ Enables or disables progress bars (via tqdm). Defaults to True.
19
+ logger : Logger
20
+ Logging instance to handle application logging.
21
+
22
+ Methods
23
+ -------
24
+ change_logger_lvl(lvl: Literal["TRACE", "DEBUG", "INFO", "WARN", "ERROR"])
25
+ Changes the logging level to the specified value.
26
+ set_overpass_url(url: str)
27
+ Sets a new Overpass API URL.
28
+ set_timeout(timeout: int)
29
+ Sets the timeout for API requests.
30
+ set_enable_tqdm(enable: bool)
31
+ Enables or disables progress bars in the application.
32
+ """
33
+ def __init__(
34
+ self,
35
+ overpass_url="http://lz4.overpass-api.de/api/interpreter",
36
+ timeout=None,
37
+ enable_tqdm_bar=True,
38
+ ):
39
+ self.overpass_url = overpass_url
40
+ self.timeout = timeout
41
+ self.enable_tqdm_bar = enable_tqdm_bar
42
+ self.logger = logger
43
+
44
+ def change_logger_lvl(self, lvl: Literal["TRACE", "DEBUG", "INFO", "WARN", "ERROR"]):
45
+ self.logger.remove()
46
+ self.logger.add(sys.stderr, level=lvl)
47
+
48
+ def set_overpass_url(self, url: str):
49
+ self.overpass_url = url
50
+
51
+ def set_timeout(self, timeout: int):
52
+ self.timeout = timeout
53
+
54
+ def set_enable_tqdm(self, enable: bool):
55
+ self.enable_tqdm_bar = enable
56
+
57
+
58
+ config = Config()
@@ -0,0 +1 @@
1
+ VERSION = "0.1.0"
File without changes
@@ -0,0 +1,73 @@
1
+ from enum import Enum
2
+ from typing import Literal
3
+
4
+
5
+ class RegistrationStatus(Enum):
6
+ """
7
+ Enum for registration status of the road.
8
+ """
9
+
10
+ FEDERAL = 1
11
+ REGIONAL = 2
12
+ LOCAL = 3
13
+
14
+
15
+ class HighwayType(Enum):
16
+ """
17
+ Enum of highway types. Properties contain registration status & max speeds
18
+ """
19
+
20
+ MOTORWAY = "motorway"
21
+ TRUNK = "trunk"
22
+ PRIMARY = "primary"
23
+ SECONDARY = "secondary"
24
+ TERTIARY = "tertiary"
25
+ UNCLASSIFIED = "unclassified"
26
+ RESIDENTIAL = "residential"
27
+ MOTORWAY_LINK = "motorway_link"
28
+ TRUNK_LINK = "trunk_link"
29
+ PRIMARY_LINK = "primary_link"
30
+ SECONDARY_LINK = "secondary_link"
31
+ TERTIARY_LINK = "tertiary_link"
32
+ LIVING_STREET = "living_street"
33
+
34
+ @property
35
+ def reg_status(self) -> Literal[1, 2, 3]:
36
+ reg_status = {
37
+ HighwayType.MOTORWAY: RegistrationStatus.FEDERAL,
38
+ HighwayType.TRUNK: RegistrationStatus.FEDERAL,
39
+ HighwayType.PRIMARY: RegistrationStatus.REGIONAL,
40
+ HighwayType.SECONDARY: RegistrationStatus.REGIONAL,
41
+ HighwayType.TERTIARY: RegistrationStatus.LOCAL,
42
+ HighwayType.UNCLASSIFIED: RegistrationStatus.LOCAL,
43
+ HighwayType.RESIDENTIAL: RegistrationStatus.LOCAL,
44
+ HighwayType.MOTORWAY_LINK: RegistrationStatus.FEDERAL,
45
+ HighwayType.TRUNK_LINK: RegistrationStatus.FEDERAL,
46
+ HighwayType.PRIMARY_LINK: RegistrationStatus.REGIONAL,
47
+ HighwayType.SECONDARY_LINK: RegistrationStatus.REGIONAL,
48
+ HighwayType.TERTIARY_LINK: RegistrationStatus.LOCAL,
49
+ HighwayType.LIVING_STREET: RegistrationStatus.LOCAL,
50
+ }
51
+ return reg_status[self].value
52
+
53
+ @property
54
+ def max_speed(self) -> float:
55
+ """
56
+ Average speed in m/min.
57
+ """
58
+ speeds = { # km/h
59
+ HighwayType.MOTORWAY: 110,
60
+ HighwayType.TRUNK: 90,
61
+ HighwayType.PRIMARY: 60,
62
+ HighwayType.SECONDARY: 60,
63
+ HighwayType.TERTIARY: 60,
64
+ HighwayType.UNCLASSIFIED: 40,
65
+ HighwayType.RESIDENTIAL: 40,
66
+ HighwayType.MOTORWAY_LINK: 90,
67
+ HighwayType.TRUNK_LINK: 90,
68
+ HighwayType.PRIMARY_LINK: 60,
69
+ HighwayType.SECONDARY_LINK: 60,
70
+ HighwayType.TERTIARY_LINK: 60,
71
+ HighwayType.LIVING_STREET: 20,
72
+ }
73
+ return speeds[self] * 1000 / 60 # metres per minute
@@ -0,0 +1,25 @@
1
+ from enum import Enum
2
+
3
+
4
+ class PublicTrasport(Enum):
5
+ """
6
+ Enumeration class for edge types in graphs.
7
+ """
8
+
9
+ SUBWAY = "subway"
10
+ BUS = "bus"
11
+ TRAM = "tram"
12
+ TROLLEYBUS = "trolleybus"
13
+
14
+ @property
15
+ def avg_speed(self) -> float:
16
+ """
17
+ Average speed in m/min.
18
+ """
19
+ speeds = { # km/h
20
+ PublicTrasport.SUBWAY: 40,
21
+ PublicTrasport.BUS: 20,
22
+ PublicTrasport.TRAM: 20,
23
+ PublicTrasport.TROLLEYBUS: 18,
24
+ }
25
+ return speeds[self] * 1000 / 60
File without changes
@@ -0,0 +1,126 @@
1
+ import geopandas as gpd
2
+ import osm2geojson
3
+ import osmnx as ox
4
+ import pandas as pd
5
+ import requests
6
+ from shapely import MultiPolygon, Polygon, unary_union
7
+
8
+ from iduedu._config import config
9
+
10
+ logger = config.logger
11
+
12
+
13
+ class RequestError(RuntimeError):
14
+ def __init__(self, message, status_code=None, reason=None, response_text=None, response_content=None):
15
+ super().__init__(message)
16
+ self.status_code = status_code
17
+ self.reason = reason
18
+ self.response_text = response_text
19
+ self.response_content = response_content
20
+
21
+ def __str__(self):
22
+ return f"{super().__str__()} (status: {self.status_code}, reason: {self.reason})"
23
+
24
+
25
+ def get_boundary_by_osm_id(osm_id) -> MultiPolygon | Polygon:
26
+ overpass_query = f"""
27
+ [out:json];
28
+ (
29
+ relation({osm_id});
30
+ );
31
+ out geom;
32
+ """
33
+ logger.debug(f"Downloading territory bounds with osm_id <{osm_id}> ...")
34
+ result = requests.get(config.overpass_url, params={"data": overpass_query}, timeout=config.timeout)
35
+ if result.status_code == 200:
36
+ json_result = result.json()
37
+ boundary = osm2geojson.json2geojson(json_result)
38
+ boundary = gpd.GeoDataFrame.from_features(boundary["features"]).set_crs(4326)
39
+ poly = unary_union(boundary.geometry)
40
+ return poly
41
+ raise RequestError(
42
+ message=f"Request failed with status code {result.status_code}, reason: {result.reason}",
43
+ status_code=result.status_code,
44
+ reason=result.reason,
45
+ response_text=result.text,
46
+ response_content=result.content,
47
+ )
48
+
49
+
50
+ def get_boundary_by_name(territory_name: str) -> Polygon | MultiPolygon:
51
+ logger.debug(f"Downloading territory bounds with name <{territory_name}> ...")
52
+ place = ox.geocode_to_gdf(territory_name)
53
+ return unary_union(place.geometry)
54
+
55
+
56
+ def get_boundary(
57
+ osm_id: int | None = None, territory_name: str | None = None, polygon: Polygon | MultiPolygon | None = None
58
+ ) -> Polygon:
59
+ """
60
+ Retrieve the boundary polygon for a given territory, either by OSM ID, territory name, or an existing polygon.
61
+ If a MultiPolygon is provided, it will be converted to a convex hull.
62
+
63
+ Parameters
64
+ ----------
65
+ osm_id : int, optional
66
+ OpenStreetMap ID of the territory to retrieve the boundary for.
67
+ Either this or `territory_name` must be provided.
68
+ territory_name : str, optional
69
+ Name of the territory to retrieve the boundary for. Either this or `osm_id` must be provided.
70
+ polygon : Polygon | MultiPolygon, optional
71
+ A custom polygon or MultiPolygon to use instead of querying by `osm_id` or `territory_name`.
72
+
73
+ Returns
74
+ -------
75
+ Polygon
76
+ The boundary polygon for the specified territory.
77
+ If a MultiPolygon was provided, it will return the convex hull.
78
+
79
+ Raises
80
+ ------
81
+ ValueError
82
+ If neither `osm_id`, `territory_name`, nor `polygon` are provided.
83
+
84
+ Examples
85
+ --------
86
+ >>> boundary = get_boundary(osm_id=123456)
87
+ >>> boundary = get_boundary(territory_name="New York")
88
+ >>> boundary = get_boundary(polygon=some_polygon)
89
+ """
90
+
91
+ if osm_id is None and territory_name is None and polygon is None:
92
+ raise ValueError("Either osm_id or name or polygon must be specified")
93
+ if osm_id:
94
+ polygon: Polygon = get_boundary_by_osm_id(osm_id)
95
+ elif territory_name:
96
+ polygon: Polygon = get_boundary_by_name(territory_name)
97
+
98
+ if isinstance(polygon, MultiPolygon):
99
+ polygon: Polygon = polygon.convex_hull
100
+
101
+ return polygon
102
+
103
+
104
+ def get_routes_by_poly(polygon: Polygon, public_transport_type: str) -> pd.DataFrame:
105
+ polygon_coords = " ".join(f"{y} {x}" for x, y in polygon.exterior.coords[:-1])
106
+ overpass_query = f"""
107
+ [out:json];
108
+ (
109
+ relation(poly:"{polygon_coords}")['route'='{public_transport_type}'];
110
+ );
111
+ out geom;
112
+ """
113
+ logger.debug(f"Downloading routes from OSM with type <{public_transport_type}> ...")
114
+ result = requests.post(config.overpass_url, data={"data": overpass_query}, timeout=config.timeout)
115
+ if result.status_code == 200:
116
+ json_result = result.json()["elements"]
117
+ data = pd.DataFrame(json_result)
118
+ data["transport_type"] = public_transport_type
119
+ return data
120
+ raise RequestError(
121
+ message=f"Request failed with status code {result.status_code}, reason: {result.reason}",
122
+ status_code=result.status_code,
123
+ reason=result.reason,
124
+ response_text=result.text,
125
+ response_content=result.content,
126
+ )