multilayer-sdk 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.
- multilayer_sdk-0.1.0/LICENSE +21 -0
- multilayer_sdk-0.1.0/PKG-INFO +193 -0
- multilayer_sdk-0.1.0/README.md +152 -0
- multilayer_sdk-0.1.0/pyproject.toml +106 -0
- multilayer_sdk-0.1.0/setup.cfg +4 -0
- multilayer_sdk-0.1.0/src/multilayer/__init__.py +51 -0
- multilayer_sdk-0.1.0/src/multilayer/cli.py +134 -0
- multilayer_sdk-0.1.0/src/multilayer/core.py +232 -0
- multilayer_sdk-0.1.0/src/multilayer/grid.py +81 -0
- multilayer_sdk-0.1.0/src/multilayer/html_builder.py +542 -0
- multilayer_sdk-0.1.0/src/multilayer/layers.py +271 -0
- multilayer_sdk-0.1.0/src/multilayer/py.typed +1 -0
- multilayer_sdk-0.1.0/src/multilayer/symbology.py +224 -0
- multilayer_sdk-0.1.0/src/multilayer_sdk.egg-info/PKG-INFO +193 -0
- multilayer_sdk-0.1.0/src/multilayer_sdk.egg-info/SOURCES.txt +18 -0
- multilayer_sdk-0.1.0/src/multilayer_sdk.egg-info/dependency_links.txt +1 -0
- multilayer_sdk-0.1.0/src/multilayer_sdk.egg-info/entry_points.txt +2 -0
- multilayer_sdk-0.1.0/src/multilayer_sdk.egg-info/requires.txt +13 -0
- multilayer_sdk-0.1.0/src/multilayer_sdk.egg-info/top_level.txt +1 -0
- multilayer_sdk-0.1.0/tests/test_multilayer.py +209 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yusuf Eminoğlu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: multilayer-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pure-Python Synchronized Multi-Panel Map Visualization, Spatial Comparison Engine, and Interactive Dashboard Builder.
|
|
5
|
+
Author-email: Yusuf Eminoğlu <yusufeminoglu@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/YusufEminoglu/multilayer-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/YusufEminoglu/multilayer-sdk
|
|
9
|
+
Project-URL: Issues, https://github.com/YusufEminoglu/multilayer-sdk/issues
|
|
10
|
+
Project-URL: Documentation, https://yusufeminoglu.github.io/multilayer-sdk/
|
|
11
|
+
Project-URL: Changelog, https://github.com/YusufEminoglu/multilayer-sdk/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: gis,multimap,multilayer,geovisualization,spatial-comparison,synchronized-maps,side-by-side,curtain-swipe,geojson,geopandas,leaflet,dashboard,urban-planning,headless
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
23
|
+
Classifier: Topic :: Multimedia :: Graphics :: Presentation
|
|
24
|
+
Classifier: Intended Audience :: Developers
|
|
25
|
+
Classifier: Intended Audience :: Science/Research
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Provides-Extra: geo
|
|
30
|
+
Requires-Dist: shapely>=2.0.0; extra == "geo"
|
|
31
|
+
Requires-Dist: geopandas>=0.12.0; extra == "geo"
|
|
32
|
+
Requires-Dist: matplotlib>=3.5.0; extra == "geo"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
<div align="center">
|
|
43
|
+
|
|
44
|
+
<img src="docs/icons/logo.svg" width="128" height="128" alt="multilayer-sdk Logo" style="filter: drop-shadow(0 12px 24px rgba(16,185,129,0.3));"/>
|
|
45
|
+
|
|
46
|
+
# multilayer-sdk
|
|
47
|
+
|
|
48
|
+
**Pure-Python Synchronized Multi-Panel Map Visualization, Spatial Comparison Engine & Interactive Dashboard Builder.**
|
|
49
|
+
|
|
50
|
+
[](https://pypi.org/project/multilayer-sdk/)
|
|
51
|
+
[](https://pypi.org/project/multilayer-sdk/)
|
|
52
|
+
[](LICENSE)
|
|
53
|
+
[](https://github.com/YusufEminoglu/multilayer-sdk/actions)
|
|
54
|
+
[](https://yusufeminoglu.github.io/multilayer-sdk/)
|
|
55
|
+
|
|
56
|
+
<br/>
|
|
57
|
+
|
|
58
|
+
<img src="docs/assets/hero.svg" width="100%" alt="multilayer-sdk Architecture"/>
|
|
59
|
+
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🌟 Overview
|
|
65
|
+
|
|
66
|
+
**multilayer-sdk** (the headless Python core behind **02Multimap**) allows urban planners, spatial data scientists, and researchers to visualize, cross-analyze, and compare multiple spatial datasets side-by-side with millisecond synchronization.
|
|
67
|
+
|
|
68
|
+
Instead of toggling layers on and off in a single map, `multilayer` coordinates up to 8 synchronized map viewports in dynamic grids (1x2, 2x1, 1x3, 2x2, 2x3, 2x4) with:
|
|
69
|
+
- **Bi-directional Pan & Zoom Broadcasting**: Drag or zoom on any panel to coordinate all others in real time.
|
|
70
|
+
- **Neon Laser Crosshair Cursor Tracking**: Move your mouse over any panel to project neon-colored crosshairs tracking the exact geographic coordinate across all viewports.
|
|
71
|
+
- **Graduated Thematic Choropleths**: Quantiles, Equal Interval, Natural Breaks, and Standard Deviation statistical binning with scientific palettes (`viridis`, `magma`, `plasma`, `turbo`, `cividis`, `spectral`, `rdylbu`).
|
|
72
|
+
- **Interactive Split-Screen Curtain Swipe**: Draggable split slider for before/after temporal change detection.
|
|
73
|
+
- **Single-File Self-Contained HTML Dashboards**: Export zero-dependency interactive HTML files ready for presentations, stakeholders, or offline field audits.
|
|
74
|
+
- **Jupyter Notebook & Google Colab Integration**: Rich inline widget display via `mm.show()`.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 🚀 Installation
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install multilayer-sdk
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## ⚡ Quickstart
|
|
87
|
+
|
|
88
|
+
### 1. Build a 4-Panel Synchronized Workspace
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import multilayer as ml
|
|
92
|
+
|
|
93
|
+
# 1. Initialize a 4-panel (2x2) synchronized map grid
|
|
94
|
+
mm = ml.MultiMap(grid="2x2", title="Urban Vulnerability & Land Use Assessment", basemap="carto-dark")
|
|
95
|
+
|
|
96
|
+
# 2. Panel 1: High-resolution satellite imagery with study area boundary
|
|
97
|
+
mm.panel(0).title = "1. Satellite Context"
|
|
98
|
+
mm.panel(0).set_basemap("satellite")
|
|
99
|
+
mm.panel(0).add_layer("study_area.geojson", stroke_color="#38bdf8", fill_opacity=0.2)
|
|
100
|
+
|
|
101
|
+
# 3. Panel 2: Thematic choropleth of population density
|
|
102
|
+
vlayer = ml.VectorLayer.from_geojson("demographics.geojson")
|
|
103
|
+
pop_choro = ml.Choropleth.classify(vlayer, property_name="density_km2", method="quantiles", color_ramp="viridis")
|
|
104
|
+
mm.panel(1).title = "2. Population Density"
|
|
105
|
+
mm.panel(1).add_layer(pop_choro)
|
|
106
|
+
|
|
107
|
+
# 4. Panel 3: Flood hazard exposure score
|
|
108
|
+
risk_choro = ml.Choropleth.classify(vlayer, property_name="flood_risk_score", method="equal_interval", color_ramp="magma")
|
|
109
|
+
mm.panel(2).title = "3. Flood Hazard Exposure"
|
|
110
|
+
mm.panel(2).add_layer(risk_choro)
|
|
111
|
+
|
|
112
|
+
# 5. Panel 4: Future 2030 Master Zoning Plan
|
|
113
|
+
mm.panel(3).title = "4. Future Master Plan 2030"
|
|
114
|
+
mm.panel(3).add_layer("zoning_plan.geojson", fill_color="#10b981", fill_opacity=0.6)
|
|
115
|
+
|
|
116
|
+
# 6. Save as standalone interactive HTML dashboard
|
|
117
|
+
mm.to_html("urban_assessment_dashboard.html")
|
|
118
|
+
|
|
119
|
+
# 7. Render inline in Jupyter Notebook / Google Colab
|
|
120
|
+
mm.show()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### 2. Draggable Split-Screen Curtain Swipe Comparison
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import multilayer as ml
|
|
129
|
+
|
|
130
|
+
swipe = ml.SwipeMap(
|
|
131
|
+
left_layer="landcover_2010.geojson",
|
|
132
|
+
right_layer="landcover_2026.geojson",
|
|
133
|
+
left_title="Historical (2010)",
|
|
134
|
+
right_title="Current (2026)",
|
|
135
|
+
basemap="satellite"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
swipe.to_html("deforestation_swipe.html")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 💻 Command Line Interface (CLI)
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# 1. Build a 2x2 synchronized dashboard from 4 GeoJSON files
|
|
147
|
+
multilayer build --layers bldgs.geojson,roads.geojson,hazard.geojson,zoning.geojson --grid 2x2 --out city_dashboard.html --open
|
|
148
|
+
|
|
149
|
+
# 2. Build a 2-panel before/after split-screen swipe comparison
|
|
150
|
+
multilayer compare flood_2020.geojson flood_2026.geojson --left-title "2020 Flood" --right-title "2026 Flood" --out flood_swipe.html
|
|
151
|
+
|
|
152
|
+
# 3. Inspect GeoJSON feature count, properties, and bounding box
|
|
153
|
+
multilayer inspect study_area.geojson
|
|
154
|
+
|
|
155
|
+
# 4. List all built-in web map tile basemaps
|
|
156
|
+
multilayer tiles
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## ⚙️ Supported Grid Matrices
|
|
162
|
+
|
|
163
|
+
| Grid Preset | Layout Dimensions | Panel Count | Primary Cartographic Use Case |
|
|
164
|
+
| :--- | :--- | :--- | :--- |
|
|
165
|
+
| **`1x2`** | 1 Row $\times$ 2 Columns | 2 Panels | Before/After comparisons, Suitability vs Actual zoning |
|
|
166
|
+
| **`2x1`** | 2 Rows $\times$ 1 Column | 2 Panels | Vertical elevation profiles, transport corridors |
|
|
167
|
+
| **`1x3`** | 1 Row $\times$ 3 Columns | 3 Panels | Past $\rightarrow$ Present $\rightarrow$ Future temporal timelines |
|
|
168
|
+
| **`2x2`** | 2 Rows $\times$ 2 Columns | 4 Panels | 4-way evaluation (Base, Demographics, Hazards, Policy) |
|
|
169
|
+
| **`2x3`** | 2 Rows $\times$ 3 Columns | 6 Panels | Multi-criteria evaluation (MCDA factor grids) |
|
|
170
|
+
| **`2x4`** | 2 Rows $\times$ 4 Columns | 8 Panels | High-density multi-scenario sensitivity snapshots |
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 📄 Academic Citation
|
|
175
|
+
|
|
176
|
+
If you use **multilayer-sdk** in scientific publications, planning projects, or research, please cite:
|
|
177
|
+
|
|
178
|
+
```bibtex
|
|
179
|
+
@software{eminoglu2026multilayer,
|
|
180
|
+
author = {Emino{\\u{g}}lu, Yusuf},
|
|
181
|
+
title = {{multilayer-sdk: Pure-Python Synchronized Multi-Panel Map Visualization, Spatial Comparison Engine, and Interactive Dashboard Builder}},
|
|
182
|
+
year = {2026},
|
|
183
|
+
publisher = {PyPI - Python Package Index},
|
|
184
|
+
version = {0.1.0},
|
|
185
|
+
url = {https://github.com/YusufEminoglu/multilayer-sdk}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 📜 License
|
|
192
|
+
|
|
193
|
+
Distributed under the **MIT** License. Copyright (c) 2026 Yusuf Eminoğlu.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="docs/icons/logo.svg" width="128" height="128" alt="multilayer-sdk Logo" style="filter: drop-shadow(0 12px 24px rgba(16,185,129,0.3));"/>
|
|
4
|
+
|
|
5
|
+
# multilayer-sdk
|
|
6
|
+
|
|
7
|
+
**Pure-Python Synchronized Multi-Panel Map Visualization, Spatial Comparison Engine & Interactive Dashboard Builder.**
|
|
8
|
+
|
|
9
|
+
[](https://pypi.org/project/multilayer-sdk/)
|
|
10
|
+
[](https://pypi.org/project/multilayer-sdk/)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
[](https://github.com/YusufEminoglu/multilayer-sdk/actions)
|
|
13
|
+
[](https://yusufeminoglu.github.io/multilayer-sdk/)
|
|
14
|
+
|
|
15
|
+
<br/>
|
|
16
|
+
|
|
17
|
+
<img src="docs/assets/hero.svg" width="100%" alt="multilayer-sdk Architecture"/>
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🌟 Overview
|
|
24
|
+
|
|
25
|
+
**multilayer-sdk** (the headless Python core behind **02Multimap**) allows urban planners, spatial data scientists, and researchers to visualize, cross-analyze, and compare multiple spatial datasets side-by-side with millisecond synchronization.
|
|
26
|
+
|
|
27
|
+
Instead of toggling layers on and off in a single map, `multilayer` coordinates up to 8 synchronized map viewports in dynamic grids (1x2, 2x1, 1x3, 2x2, 2x3, 2x4) with:
|
|
28
|
+
- **Bi-directional Pan & Zoom Broadcasting**: Drag or zoom on any panel to coordinate all others in real time.
|
|
29
|
+
- **Neon Laser Crosshair Cursor Tracking**: Move your mouse over any panel to project neon-colored crosshairs tracking the exact geographic coordinate across all viewports.
|
|
30
|
+
- **Graduated Thematic Choropleths**: Quantiles, Equal Interval, Natural Breaks, and Standard Deviation statistical binning with scientific palettes (`viridis`, `magma`, `plasma`, `turbo`, `cividis`, `spectral`, `rdylbu`).
|
|
31
|
+
- **Interactive Split-Screen Curtain Swipe**: Draggable split slider for before/after temporal change detection.
|
|
32
|
+
- **Single-File Self-Contained HTML Dashboards**: Export zero-dependency interactive HTML files ready for presentations, stakeholders, or offline field audits.
|
|
33
|
+
- **Jupyter Notebook & Google Colab Integration**: Rich inline widget display via `mm.show()`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🚀 Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install multilayer-sdk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## ⚡ Quickstart
|
|
46
|
+
|
|
47
|
+
### 1. Build a 4-Panel Synchronized Workspace
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import multilayer as ml
|
|
51
|
+
|
|
52
|
+
# 1. Initialize a 4-panel (2x2) synchronized map grid
|
|
53
|
+
mm = ml.MultiMap(grid="2x2", title="Urban Vulnerability & Land Use Assessment", basemap="carto-dark")
|
|
54
|
+
|
|
55
|
+
# 2. Panel 1: High-resolution satellite imagery with study area boundary
|
|
56
|
+
mm.panel(0).title = "1. Satellite Context"
|
|
57
|
+
mm.panel(0).set_basemap("satellite")
|
|
58
|
+
mm.panel(0).add_layer("study_area.geojson", stroke_color="#38bdf8", fill_opacity=0.2)
|
|
59
|
+
|
|
60
|
+
# 3. Panel 2: Thematic choropleth of population density
|
|
61
|
+
vlayer = ml.VectorLayer.from_geojson("demographics.geojson")
|
|
62
|
+
pop_choro = ml.Choropleth.classify(vlayer, property_name="density_km2", method="quantiles", color_ramp="viridis")
|
|
63
|
+
mm.panel(1).title = "2. Population Density"
|
|
64
|
+
mm.panel(1).add_layer(pop_choro)
|
|
65
|
+
|
|
66
|
+
# 4. Panel 3: Flood hazard exposure score
|
|
67
|
+
risk_choro = ml.Choropleth.classify(vlayer, property_name="flood_risk_score", method="equal_interval", color_ramp="magma")
|
|
68
|
+
mm.panel(2).title = "3. Flood Hazard Exposure"
|
|
69
|
+
mm.panel(2).add_layer(risk_choro)
|
|
70
|
+
|
|
71
|
+
# 5. Panel 4: Future 2030 Master Zoning Plan
|
|
72
|
+
mm.panel(3).title = "4. Future Master Plan 2030"
|
|
73
|
+
mm.panel(3).add_layer("zoning_plan.geojson", fill_color="#10b981", fill_opacity=0.6)
|
|
74
|
+
|
|
75
|
+
# 6. Save as standalone interactive HTML dashboard
|
|
76
|
+
mm.to_html("urban_assessment_dashboard.html")
|
|
77
|
+
|
|
78
|
+
# 7. Render inline in Jupyter Notebook / Google Colab
|
|
79
|
+
mm.show()
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### 2. Draggable Split-Screen Curtain Swipe Comparison
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import multilayer as ml
|
|
88
|
+
|
|
89
|
+
swipe = ml.SwipeMap(
|
|
90
|
+
left_layer="landcover_2010.geojson",
|
|
91
|
+
right_layer="landcover_2026.geojson",
|
|
92
|
+
left_title="Historical (2010)",
|
|
93
|
+
right_title="Current (2026)",
|
|
94
|
+
basemap="satellite"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
swipe.to_html("deforestation_swipe.html")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 💻 Command Line Interface (CLI)
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# 1. Build a 2x2 synchronized dashboard from 4 GeoJSON files
|
|
106
|
+
multilayer build --layers bldgs.geojson,roads.geojson,hazard.geojson,zoning.geojson --grid 2x2 --out city_dashboard.html --open
|
|
107
|
+
|
|
108
|
+
# 2. Build a 2-panel before/after split-screen swipe comparison
|
|
109
|
+
multilayer compare flood_2020.geojson flood_2026.geojson --left-title "2020 Flood" --right-title "2026 Flood" --out flood_swipe.html
|
|
110
|
+
|
|
111
|
+
# 3. Inspect GeoJSON feature count, properties, and bounding box
|
|
112
|
+
multilayer inspect study_area.geojson
|
|
113
|
+
|
|
114
|
+
# 4. List all built-in web map tile basemaps
|
|
115
|
+
multilayer tiles
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## ⚙️ Supported Grid Matrices
|
|
121
|
+
|
|
122
|
+
| Grid Preset | Layout Dimensions | Panel Count | Primary Cartographic Use Case |
|
|
123
|
+
| :--- | :--- | :--- | :--- |
|
|
124
|
+
| **`1x2`** | 1 Row $\times$ 2 Columns | 2 Panels | Before/After comparisons, Suitability vs Actual zoning |
|
|
125
|
+
| **`2x1`** | 2 Rows $\times$ 1 Column | 2 Panels | Vertical elevation profiles, transport corridors |
|
|
126
|
+
| **`1x3`** | 1 Row $\times$ 3 Columns | 3 Panels | Past $\rightarrow$ Present $\rightarrow$ Future temporal timelines |
|
|
127
|
+
| **`2x2`** | 2 Rows $\times$ 2 Columns | 4 Panels | 4-way evaluation (Base, Demographics, Hazards, Policy) |
|
|
128
|
+
| **`2x3`** | 2 Rows $\times$ 3 Columns | 6 Panels | Multi-criteria evaluation (MCDA factor grids) |
|
|
129
|
+
| **`2x4`** | 2 Rows $\times$ 4 Columns | 8 Panels | High-density multi-scenario sensitivity snapshots |
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 📄 Academic Citation
|
|
134
|
+
|
|
135
|
+
If you use **multilayer-sdk** in scientific publications, planning projects, or research, please cite:
|
|
136
|
+
|
|
137
|
+
```bibtex
|
|
138
|
+
@software{eminoglu2026multilayer,
|
|
139
|
+
author = {Emino{\\u{g}}lu, Yusuf},
|
|
140
|
+
title = {{multilayer-sdk: Pure-Python Synchronized Multi-Panel Map Visualization, Spatial Comparison Engine, and Interactive Dashboard Builder}},
|
|
141
|
+
year = {2026},
|
|
142
|
+
publisher = {PyPI - Python Package Index},
|
|
143
|
+
version = {0.1.0},
|
|
144
|
+
url = {https://github.com/YusufEminoglu/multilayer-sdk}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 📜 License
|
|
151
|
+
|
|
152
|
+
Distributed under the **MIT** License. Copyright (c) 2026 Yusuf Eminoğlu.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "multilayer-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Yusuf Eminoğlu", email = "yusufeminoglu@gmail.com" }
|
|
10
|
+
]
|
|
11
|
+
description = "Pure-Python Synchronized Multi-Panel Map Visualization, Spatial Comparison Engine, and Interactive Dashboard Builder."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
|
+
keywords = [
|
|
16
|
+
"gis",
|
|
17
|
+
"multimap",
|
|
18
|
+
"multilayer",
|
|
19
|
+
"geovisualization",
|
|
20
|
+
"spatial-comparison",
|
|
21
|
+
"synchronized-maps",
|
|
22
|
+
"side-by-side",
|
|
23
|
+
"curtain-swipe",
|
|
24
|
+
"geojson",
|
|
25
|
+
"geopandas",
|
|
26
|
+
"leaflet",
|
|
27
|
+
"dashboard",
|
|
28
|
+
"urban-planning",
|
|
29
|
+
"headless",
|
|
30
|
+
]
|
|
31
|
+
classifiers = [
|
|
32
|
+
"Development Status :: 4 - Beta",
|
|
33
|
+
"Programming Language :: Python :: 3",
|
|
34
|
+
"Programming Language :: Python :: 3.9",
|
|
35
|
+
"Programming Language :: Python :: 3.10",
|
|
36
|
+
"Programming Language :: Python :: 3.11",
|
|
37
|
+
"Programming Language :: Python :: 3.12",
|
|
38
|
+
"Programming Language :: Python :: 3.13",
|
|
39
|
+
"Operating System :: OS Independent",
|
|
40
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
41
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
42
|
+
"Topic :: Multimedia :: Graphics :: Presentation",
|
|
43
|
+
"Intended Audience :: Developers",
|
|
44
|
+
"Intended Audience :: Science/Research",
|
|
45
|
+
]
|
|
46
|
+
dependencies = []
|
|
47
|
+
|
|
48
|
+
[project.optional-dependencies]
|
|
49
|
+
geo = [
|
|
50
|
+
"shapely>=2.0.0",
|
|
51
|
+
"geopandas>=0.12.0",
|
|
52
|
+
"matplotlib>=3.5.0",
|
|
53
|
+
]
|
|
54
|
+
dev = [
|
|
55
|
+
"pytest>=7.0.0",
|
|
56
|
+
"pytest-cov>=4.0.0",
|
|
57
|
+
"ruff>=0.1.0",
|
|
58
|
+
"mypy>=1.0.0",
|
|
59
|
+
"build>=1.0.0",
|
|
60
|
+
"twine>=5.0.0",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[project.scripts]
|
|
64
|
+
multilayer = "multilayer.cli:main"
|
|
65
|
+
|
|
66
|
+
[project.urls]
|
|
67
|
+
Homepage = "https://github.com/YusufEminoglu/multilayer-sdk"
|
|
68
|
+
Repository = "https://github.com/YusufEminoglu/multilayer-sdk"
|
|
69
|
+
Issues = "https://github.com/YusufEminoglu/multilayer-sdk/issues"
|
|
70
|
+
Documentation = "https://yusufeminoglu.github.io/multilayer-sdk/"
|
|
71
|
+
Changelog = "https://github.com/YusufEminoglu/multilayer-sdk/blob/main/CHANGELOG.md"
|
|
72
|
+
|
|
73
|
+
[tool.setuptools]
|
|
74
|
+
package-dir = {"" = "src"}
|
|
75
|
+
|
|
76
|
+
[tool.setuptools.packages.find]
|
|
77
|
+
where = ["src"]
|
|
78
|
+
|
|
79
|
+
[tool.setuptools.package-data]
|
|
80
|
+
multilayer = ["py.typed"]
|
|
81
|
+
|
|
82
|
+
[tool.ruff]
|
|
83
|
+
line-length = 100
|
|
84
|
+
target-version = "py39"
|
|
85
|
+
exclude = [
|
|
86
|
+
".venv",
|
|
87
|
+
"build",
|
|
88
|
+
"dist",
|
|
89
|
+
"build_multilayer_docs.py",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
[tool.ruff.lint]
|
|
93
|
+
select = ["E", "F", "W", "I", "B", "C4"]
|
|
94
|
+
ignore = ["E501"]
|
|
95
|
+
|
|
96
|
+
[tool.pytest.ini_options]
|
|
97
|
+
testpaths = ["tests"]
|
|
98
|
+
pythonpath = ["src"]
|
|
99
|
+
|
|
100
|
+
[tool.coverage.run]
|
|
101
|
+
source = ["multilayer"]
|
|
102
|
+
branch = true
|
|
103
|
+
|
|
104
|
+
[tool.coverage.report]
|
|
105
|
+
show_missing = true
|
|
106
|
+
fail_under = 70
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
multilayer — Pure-Python Synchronized Multi-Panel Map Visualization & Spatial Comparison Engine.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from multilayer.core import MultiMap, Panel, SwipeMap, create_multimap
|
|
9
|
+
from multilayer.grid import GridLayout, GridPreset
|
|
10
|
+
from multilayer.layers import (
|
|
11
|
+
Layer,
|
|
12
|
+
RasterLayer,
|
|
13
|
+
TileLayer,
|
|
14
|
+
TileProvider,
|
|
15
|
+
VectorLayer,
|
|
16
|
+
get_tile_provider,
|
|
17
|
+
)
|
|
18
|
+
from multilayer.symbology import (
|
|
19
|
+
CategoricalStyle,
|
|
20
|
+
Choropleth,
|
|
21
|
+
ClassificationMethod,
|
|
22
|
+
ColorRamp,
|
|
23
|
+
GraduatedStyle,
|
|
24
|
+
StyleRule,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__version__ = "0.1.0"
|
|
28
|
+
__author__ = "Yusuf Eminoğlu"
|
|
29
|
+
__email__ = "yusufeminoglu@gmail.com"
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"MultiMap",
|
|
33
|
+
"Panel",
|
|
34
|
+
"SwipeMap",
|
|
35
|
+
"create_multimap",
|
|
36
|
+
"GridLayout",
|
|
37
|
+
"GridPreset",
|
|
38
|
+
"Layer",
|
|
39
|
+
"VectorLayer",
|
|
40
|
+
"RasterLayer",
|
|
41
|
+
"TileLayer",
|
|
42
|
+
"TileProvider",
|
|
43
|
+
"get_tile_provider",
|
|
44
|
+
"Choropleth",
|
|
45
|
+
"ClassificationMethod",
|
|
46
|
+
"ColorRamp",
|
|
47
|
+
"StyleRule",
|
|
48
|
+
"GraduatedStyle",
|
|
49
|
+
"CategoricalStyle",
|
|
50
|
+
"__version__",
|
|
51
|
+
]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Command Line Interface for multilayer-sdk."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import webbrowser
|
|
11
|
+
|
|
12
|
+
from multilayer import __version__
|
|
13
|
+
from multilayer.core import MultiMap, SwipeMap
|
|
14
|
+
from multilayer.layers import TileProvider, VectorLayer
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main(args: list[str] | None = None) -> int:
|
|
18
|
+
"""Main CLI entrypoint."""
|
|
19
|
+
parser = argparse.ArgumentParser(
|
|
20
|
+
prog="multilayer",
|
|
21
|
+
description="Pure-Python Synchronized Multi-Panel Map Visualization & Spatial Comparison CLI.",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
24
|
+
|
|
25
|
+
subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
|
|
26
|
+
|
|
27
|
+
# Subcommand: build
|
|
28
|
+
build_parser = subparsers.add_parser("build", help="Build synchronized multi-panel HTML dashboard")
|
|
29
|
+
build_parser.add_argument("--layers", "-l", required=True, help="Comma-separated list of GeoJSON layer paths")
|
|
30
|
+
build_parser.add_argument("--grid", "-g", default="2x2", help="Grid preset: 1x2, 2x1, 1x3, 2x2, 2x3, 2x4 (default: 2x2)")
|
|
31
|
+
build_parser.add_argument("--title", "-t", default="MultiMap Workspace", help="Dashboard title")
|
|
32
|
+
build_parser.add_argument("--basemap", "-b", default="carto-dark-matter", help="Basemap provider (default: carto-dark-matter)")
|
|
33
|
+
build_parser.add_argument("--theme", default="dark", choices=["dark", "light"], help="Color theme (default: dark)")
|
|
34
|
+
build_parser.add_argument("--out", "-o", default="multimap_dashboard.html", help="Output HTML file path")
|
|
35
|
+
build_parser.add_argument("--open", action="store_true", help="Open generated HTML dashboard in default browser")
|
|
36
|
+
|
|
37
|
+
# Subcommand: compare (Swipe Map)
|
|
38
|
+
compare_parser = subparsers.add_parser("compare", help="Create split-screen curtain swipe map")
|
|
39
|
+
compare_parser.add_argument("left_layer", help="Left / Before GeoJSON file path")
|
|
40
|
+
compare_parser.add_argument("right_layer", help="Right / After GeoJSON file path")
|
|
41
|
+
compare_parser.add_argument("--left-title", default="Before", help="Left layer title")
|
|
42
|
+
compare_parser.add_argument("--right-title", default="After", help="Right layer title")
|
|
43
|
+
compare_parser.add_argument("--out", "-o", default="swipe_map.html", help="Output HTML file path")
|
|
44
|
+
compare_parser.add_argument("--open", action="store_true", help="Open generated swipe map in browser")
|
|
45
|
+
|
|
46
|
+
# Subcommand: inspect
|
|
47
|
+
inspect_parser = subparsers.add_parser("inspect", help="Inspect GeoJSON layer features, bounding box, and properties")
|
|
48
|
+
inspect_parser.add_argument("input_file", help="GeoJSON file path to inspect")
|
|
49
|
+
|
|
50
|
+
# Subcommand: tiles
|
|
51
|
+
subparsers.add_parser("tiles", help="List all built-in web map tile basemaps")
|
|
52
|
+
|
|
53
|
+
parsed_args = parser.parse_args(args)
|
|
54
|
+
|
|
55
|
+
if not parsed_args.command:
|
|
56
|
+
parser.print_help()
|
|
57
|
+
return 0
|
|
58
|
+
|
|
59
|
+
if parsed_args.command == "build":
|
|
60
|
+
layer_paths = [p.strip() for p in parsed_args.layers.split(",") if p.strip()]
|
|
61
|
+
mm = MultiMap(
|
|
62
|
+
grid=parsed_args.grid,
|
|
63
|
+
title=parsed_args.title,
|
|
64
|
+
theme=parsed_args.theme,
|
|
65
|
+
basemap=parsed_args.basemap,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
for idx, lp in enumerate(layer_paths):
|
|
69
|
+
if not os.path.exists(lp):
|
|
70
|
+
print(f"Error: Layer file not found: {lp}", file=sys.stderr)
|
|
71
|
+
return 1
|
|
72
|
+
if idx < mm.panel_count:
|
|
73
|
+
vlyr = VectorLayer.from_geojson(lp)
|
|
74
|
+
mm.panel(idx).add_layer(vlyr)
|
|
75
|
+
mm.panel(idx).title = vlyr.name
|
|
76
|
+
|
|
77
|
+
out_path = os.path.abspath(parsed_args.out)
|
|
78
|
+
mm.to_html(out_path)
|
|
79
|
+
print(f"✓ Synchronized MultiMap dashboard generated: {out_path} ({os.path.getsize(out_path):,} bytes)")
|
|
80
|
+
|
|
81
|
+
if parsed_args.open:
|
|
82
|
+
webbrowser.open(f"file://{out_path}")
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
elif parsed_args.command == "compare":
|
|
86
|
+
if not os.path.exists(parsed_args.left_layer) or not os.path.exists(parsed_args.right_layer):
|
|
87
|
+
print("Error: One or both layer files not found.", file=sys.stderr)
|
|
88
|
+
return 1
|
|
89
|
+
|
|
90
|
+
sm = SwipeMap(
|
|
91
|
+
left_layer=parsed_args.left_layer,
|
|
92
|
+
right_layer=parsed_args.right_layer,
|
|
93
|
+
left_title=parsed_args.left_title,
|
|
94
|
+
right_title=parsed_args.right_title,
|
|
95
|
+
)
|
|
96
|
+
out_path = os.path.abspath(parsed_args.out)
|
|
97
|
+
sm.to_html(out_path)
|
|
98
|
+
print(f"✓ Split-screen Swipe Map generated: {out_path} ({os.path.getsize(out_path):,} bytes)")
|
|
99
|
+
|
|
100
|
+
if parsed_args.open:
|
|
101
|
+
webbrowser.open(f"file://{out_path}")
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
elif parsed_args.command == "inspect":
|
|
105
|
+
if not os.path.exists(parsed_args.input_file):
|
|
106
|
+
print(f"Error: File not found: {parsed_args.input_file}", file=sys.stderr)
|
|
107
|
+
return 1
|
|
108
|
+
with open(parsed_args.input_file, "r", encoding="utf-8") as f:
|
|
109
|
+
data = json.load(f)
|
|
110
|
+
v = VectorLayer.from_geojson(data, name=parsed_args.input_file)
|
|
111
|
+
feats = data.get("features", [])
|
|
112
|
+
bounds = v.get_bounds()
|
|
113
|
+
props_keys = set()
|
|
114
|
+
for ft in feats:
|
|
115
|
+
props_keys.update(ft.get("properties", {}).keys())
|
|
116
|
+
|
|
117
|
+
print(f"Layer File : {parsed_args.input_file}")
|
|
118
|
+
print(f"Feature Count : {len(feats):,}")
|
|
119
|
+
print(f"Bounding Box : {bounds}")
|
|
120
|
+
print(f"Properties ({len(props_keys)}) : {', '.join(sorted(props_keys))}")
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
elif parsed_args.command == "tiles":
|
|
124
|
+
print("Available Built-in Tile Providers in multilayer-sdk:")
|
|
125
|
+
for tp in TileProvider:
|
|
126
|
+
if tp != TileProvider.NONE:
|
|
127
|
+
print(f" - {tp.value:<20} : {tp.url_template}")
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
return 0
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
sys.exit(main())
|