Contributing to UrbanMapper
¶
Welcome to the contributing guide
for UrbanMapper! We are excited to collaborate on developing a tool for urban
data analysis that is both accessible and powerful. This guide will help you set up your environment, add new
components, and submit contributions. Whether you are fixing bugs
, adding features
, or improving documentation
,
your work is important!
Status of UrbanMapper
UrbanMapper
is actively evolving. Expect changes, and if you hit a snag, open a GitHub Issue—we’re here to help!
New Contributors
Check out the GitHub Issues for good first tasks or reach out for guidance!
Project Setup Guide¶
Prerequisites¶
UrbanMapper
requires Python3.10
or higher.-
Use
uv
(recommended),conda
, orvenv
to manage your project setup. Follow the steps below to install one of them.- To install
uv
, follow the instructions on the uv documentation. - If you don’t have Python
3.10
or higher / you prefer to be sure, you can install and pin it using uv:
- To install
conda
, follow the instructions on the conda documentation. - If you don’t have Python
3.10
or higher, you can create a new conda environment with the required version:
- Ensure you have Python
3.10
or higher installed. You can check your Python version with: - To create a virtual environment, use Python's built-in
venv
module:
- To install
Clone the Repo¶
Environment Setup¶
Get started by setting up your development environment. We recommend uv
for its speed, but pip
or conda
work too. Choose one of the following options:
-
Lock and sync dependencies:
Note: If you encounter errors related to 'cairo' during dependency installation, see the Troubleshooting section. -
(Recommended) Install Jupyter extensions for interactive visualisations requiring Jupyter widgets:
-
Launch Jupyter Lab to explore
UrbanMapper
(faster than running Jupyter without uv):
UV's readings recommendations:
If you prefer not to use uv
, you can install UrbanMapper
using pip
. This method is slower and requires more manual intervention.
Assumptions:
- You have
pip
installed. - You are working within a virtual environment or a conda environment.
Note
If you are not using a virtual or conda environment, it is highly recommended to set one up to avoid conflicts. Refer to Python's venv documentation or conda's environment management guide for assistance.
- Install dependencies:
-
Install
UrbanMapper
:Thepip install -e ./UrbanMapper # or if you ensure you are in your virtual environment, cd UrbanMapper && pip install -e .
-e
flag installsUrbanMapper
in editable mode, allowing changes to the code to be reflected immediately. If you don’t need this, usepip install ./UrbanMapper
instead. -
(Recommended) Install Jupyter extensions for interactive visualisations:
-
Launch Jupyter Lab:
Config Note:¶
Check out config.yaml
in urban_mapper/
for pipeline schemas and mixin mappings. It’s optional for basic setup but key for advanced tweaks.
Alternative Tools
Prefer pip
or conda
? That’s fine—just note uv
is our go-to for performance.
Linting and Formatting with Ruff¶
We use ruff
to keep the codebase clean and consistent. Run it before submitting changes.
Commands¶
- Check Issues:
- Fix Formatting:
Editor Integration
Integrate ruff
into your editor (e.g., VSCode) for live feedback.
Pre-Commit Hooks¶
Pre-commit hooks enforce standards by running checks (like ruff
) before commits.
Setup¶
- Install:
- Test Manually (optional):
Automatic Execution
Hooks run automatically on git commit
. Fix any failures to proceed.
How to Create New Components¶
UrbanMapper
’s modular design makes extending it a breeze. Select the component type you want to add:
Loaders pull data (e.g., CSV, Shapefiles) into a GeoDataFrame
.
- Subclass
LoaderBase
(urban_mapper/modules/loader/abc_loader.py
):- Implement
load_data_from_file
. Refer to thebase
class for details.
- Implement
- Register It:
- Add to
FILE_LOADER_FACTORY
inurban_mapper/modules/loader/loader_factory.py
.
- Add to
Example (csv_loader.py
):
from urban_mapper.modules.loader.abc_loader import LoaderBase
import geopandas as gpd
import pandas as pd
from beartype import beartype
@beartype
class CSVLoader(LoaderBase):
def load_data_from_file(self) -> gpd.GeoDataFrame:
df = pd.read_csv(self.file_path) #(1)
# Convert to GeoDataFrame...
return gdf
-
Reads the CSV file into a pandas DataFrame before geospatial conversion.
-
Place in
urban_mapper/modules/loader/loaders/
.
Urban layers (e.g., streets) are spatial entities as GeoDataFrames
.
- Subclass
UrbanLayerBase
(urban_mapper/modules/urban_layer/abc_urban_layer.py
): - Add methods like
from_place
and_map_nearest_layer
. Refer to thebase
class for details.
Example (osmnx_streets.py
):
from urban_mapper.modules.urban_layer.abc_urban_layer import UrbanLayerBase
import geopandas as gpd
import osmnx as ox
from beartype import beartype
@beartype
class OSMNXStreets(UrbanLayerBase):
def from_place(self, place_name: str, **kwargs) -> None:
self.network = ox.graph_from_place(place_name, network_type="all") # (1)
self.layer = ox.graph_to_gdfs(self.network)[1].to_crs(self.coordinate_reference_system)
-
Fetches street network data using OSMnx for the specified place.
-
Place in
urban_mapper/modules/urban_layer/urban_layers/
. - Auto-detected—no registration needed.
Imputers fill missing geospatial data, such as gaps in a dataset.
- Subclass
GeoImputerBase
(urban_mapper/modules/imputer/abc_imputer.py
): - Implement
_transform
andpreview
. Refer to thebase
class for details.
Example (simple_geo_imputer.py
):
from urban_mapper.modules.imputer.abc_imputer import GeoImputerBase
import geopandas as gpd
from beartype import beartype
@beartype
class SimpleGeoImputer(GeoImputerBase):
def _transform(self, input_geodataframe: gpd.GeoDataFrame, urban_layer) -> gpd.GeoDataFrame:
# Impute logic here
return input_geodataframe def preview(rmat: s r = "-> str::
s r return f"Imputer: Simpl:s r eGeoImputer\n Lat: {self.la:titude_column}"
- Place: in
urban_mapper/modules/imputer/imputers/
. - Auto-detected.
Filters refine datasets (e.g., by spatial bounds).
- Subclass
GeoFilterBase
(urban_mapper/modules/filter/abc_filter.py
): - Implement
_transform
. Refer to thebase
class for details.
Example (bounding_box_filter.py
):
from urban_mapper.modules.filter.abc_filter import GeoFilterBase
import geopandas as gpd
from beartype import beartype
@beartype
class BoundingBoxFilter(GeoFilterBase):
def _transform(self, input_geodataframe: gpd.GeoDataFrame, urban_layer) -> gpd.GeoDataFrame:
minx, miny, maxx, maxy = urban_layer.get_layer_bounding_box()
return input_geodataframe.cx[minx:maxx, miny:maxy]
- Place in
urban_mapper/modules/filter/filters/
. - Auto-detected.
Enrichers enhance urban layers with insights; aggregators summarise data.
Enrichers:
- Subclass
EnricherBase
(urban_mapper/modules/enricher/abc_enricher.py
). - Place in
urban_mapper/modules/enricher/enrichers/
. - Auto-detected.
Aggregators:
- Subclass
BaseAggregator
(urban_mapper/modules/enricher/aggregator/abc_aggregator.py
). - Update
EnricherFactory.build()
inurban_mapper/modules/enricher/enricher_factory.py
.
Example (sum_aggregator.py
):
from urban_mapper.modules.enricher.aggregator.abc_aggregator import BaseAggregator
import pandas as pd
from beartype import beartype
@beartype
class SumAggregator(BaseAggregator):
def __init__(self, group_by_column: str, value_column: str):
self.group_by_column = group_by_column
self.value_column = value_column
def _aggregate(self, input_dataframe: pd.DataFrame) -> pd.Series:
return input_dataframe.groupby(self.group_by_column)[self.value_column].sum()
- Place in
urban_mapper/modules/enricher/aggregator/aggregators/
. - Add to
EnricherFactory
:
Visualisers render maps for analysis.
- Subclass
VisualiserBase
(urban_mapper/modules/visualiser/abc_visualiser.py
): - Implement
_render
. Refer to thebase
class for details.
Example (static_visualiser.py
):
from urban_mapper.modules.visualiser.abc_visualiser import VisualiserBase
import geopandas as gpd
from beartype import beartype
@beartype
class StaticVisualiser(VisualiserBase):
def _render(self, urban_layer_geodataframe: gpd.GeoDataFrame, columns: list, **kwargs):
return urban_layer_geodataframe.plot(column=columns[0], legend=True, **kwargs).get_figure()
- Place in
urban_mapper/modules/visualiser/visualisers/
. - Auto-detected.
Generators create pipeline steps dynamically.
- Subclass
PipelineGeneratorBase
(urban_mapper/modules/pipeline_generator/abc_pipeline_generator.py
).
Example (gpt4o_pipeline_generator.py
):
from urban_mapper.modules.pipeline_generator.abc_pipeline_generator import PipelineGeneratorBase
from beartype import beartype
@beartype
class GPT4OPipelineGenerator(PipelineGeneratorBase):
def generate_pipeline(self, data_description: str) -> list:
# AI-driven step generation
return []
- Place in
urban_mapper/modules/pipeline_generator/generators/
. - Auto-detected via
pipeline_generator_factory.py
.
Pipeline Architecture¶
UrbanMapper
’s pipeline flows like this:
Notation: (1) = exactly one instance, (0..*) = zero or more instances, (1..*) = one or more instances, (0, 1) = zero or one instance
Each step processes the data sequentially, transforming it from raw input to enriched urban insights. New components
should slot into this sequence (see urban_mapper/pipeline/
).
Generate Documentation¶
First and foremost, thank you for your contribution! To generate documentation, follow these steps:
-
UV Sync with DEV:
-
Build Docs:
-
Serve Docs:
-
Open in Browser: Localhost
Note: During documentation generation, ensure your environment is correctly set up. If you encounter issues with dependencies like 'cairo', refer to the Troubleshooting section. If you’ve added new packages, update the requirements files as described in Managing Dependencies.
Pull Requests and Rebasing¶
- Branch:
- Commit:
- Use Git Karma style (e.g.,
feat: add new loader
).
- Use Git Karma style (e.g.,
- Rebase:
- Submit PR:
- Push and open a PR against
main
. - Note: We highly encourage using Git Karma for commits/branches (e.g.,
feat/add-loader
).
- Push and open a PR against
PR for the being are merge commits without squashing
We are currently using merge commits without squashing. This may change when UM
becomes more stable.
Therefore, make sure your history is clean enough and does not provide a spaghetti-style history.
Interested in further readings? Look here.
Common Git Commands¶
Command | Description |
---|---|
git clone <url> |
Clone the repository |
git checkout -b <name> |
Create and switch to a new branch |
git add <files> |
Stage changes for commit |
git commit -m "<msg>" |
Commit changes with a message |
git push origin <branch> |
Push changes to the remote repository |
git fetch origin |
Fetch latest changes from remote |
git rebase origin/main |
Rebase your branch on top of main |
Git Hints
- Use
git rebase -i
to polish commits, but don’t rewrite shared history. - We may request history rewritting for clarity. Beginner to fixup ? Read this nice article: https://github.com/TheAssemblyArmada/Thyme/wiki/Using-Fixup-Commits
- Want to have a nice look at your logs? Use
Tig
to visualize your git history. Install it viabrew install tig
and runtig
in your terminal.
Thank You!¶
Thanks for contributing to UrbanMapper
! Your efforts shape urban data analysis. Questions? Open an issue—we’ve got
your back.
Enjoy mapping! 🌍
Troubleshooting¶
Handling 'cairo' Dependency Issues¶
Warning
On MacOS, if you encounter errors related to 'cairo', follow these steps:
Source: https://github.com/squidfunk/mkdocs-material/issues/5121.
- Install 'cairo' if not already installed:
- If already installed, try reinstalling:
- If the symlink is broken, fix it: Or:
- For MacOS with M2 and before (Intel included):
- Set the environment variable:
- For MacOS with M3 chip or later:
- Create a symbolic link:
Note: These steps have been tested on MacOS. For Windows or Linux, adapt accordingly or open a GitHub issue.
Managing Dependencies¶
When you add a new package to the project, update the requirements files as follows:
-
Update
Note that this step will vanish after the first release of UrbanMapper on PyPi. Meanwhile, if you want to know more about why we should do that and, why notrequirements.txt
–– Mainly for the people who are usingpip
orconda
to install the dependencies.UV
is automatically doing that: https://github.com/astral-sh/uv/issues/6007. Happy reading 💪! -
Update
requirements-dev.txt
–– Mainly for the newly generated documentation via ReadTheDoc.uv export --dev --no-hashes --no-header --no-annotate | awk '{print $1}' FS=' ;' > requirements-dev.txt
Then, manually adjust for platform-specific dependencies. Locate lines like:
Replace them with:
pywin32==310; platform_system=="Windows" pywin32-ctypes==0.2.3; platform_system=="Windows" pywinpty==2.0.15; platform_system=="Windows"
Note that this manual adjustment is needed for Read The Docs, which does not yet support
uv
. See the ongoing discussion at https://github.com/astral-sh/uv/issues/10074. -
Commit and Push: After updating, commit and push the changes to the repository.