OpenLayers + Qt (QWebEngine) mapping widget for Python
Project description
pyopenlayersqt
OpenLayers + Qt (QWebEngine) mapping widget for Python.
A high-performance, feature-rich mapping widget that embeds OpenLayers in a Qt application using QWebEngine. Designed for displaying and interacting with large volumes of geospatial data.
Features
- 🗺️ Interactive Map Widget: Fully-featured OpenLayers map embedded in PySide6/Qt
- ⚡ High-Performance Rendering: Fast points layers with spatial indexing for millions of points
- 🎨 Rich Styling: Customizable styles for points, polygons, circles, and ellipses
- 📍 Geolocation Support: Fast geo-points layer with uncertainty ellipses
- 🌐 WMS Integration: Built-in Web Map Service layer support
- 🖼️ Raster Overlays: PNG/image overlay support with custom bounds
- ✅ Feature Selection: Interactive feature selection with Python ↔ JavaScript sync
- 📊 Feature Table Widget: High-performance table widget for displaying and managing features
- 🔄 Bidirectional Sync: Seamless selection synchronization between map and table
- 📏 Distance Measurement: Interactive measurement mode with geodesic distance calculations and great-circle path visualization
- 🎚️ Range Slider Widget: Dual-handle range slider for filtering features by numeric or timestamp ranges
Installation
pip install pyopenlayersqt
Requirements
- Python >= 3.10
- PySide6 >= 6.5
- numpy >= 1.23
- pillow >= 10.0
- matplotlib >= 3.7
Quick Start
from PySide6 import QtWidgets
from pyopenlayersqt import OLMapWidget, PointStyle
import sys
app = QtWidgets.QApplication(sys.argv)
# Create the map widget with custom initial view
map_widget = OLMapWidget(center=(37.0, -120.0), zoom=6)
# Add a vector layer
vector_layer = map_widget.add_vector_layer("my_layer", selectable=True)
# Add some points
coords = [(37.7749, -122.4194), (34.0522, -118.2437)] # SF, LA
vector_layer.add_points(
coords,
ids=["sf", "la"],
style=PointStyle(radius=8.0, fill_color="#ff3333")
)
# Show the map
map_widget.show()
sys.exit(app.exec())
See the examples directory for more working examples.
Core Components
OLMapWidget
The main widget class that embeds an OpenLayers map.
from pyopenlayersqt import OLMapWidget
# Create with default world view (center at 0,0, zoom level 2)
map_widget = OLMapWidget()
# Or create with custom initial view
map_widget = OLMapWidget(center=(37.0, -120.0), zoom=6)
Constructor Parameters:
parent- Optional parent widgetcenter- Initial map center as(lat, lon)tuple. Defaults to(0, 0).zoom- Initial zoom level (integer). Defaults to2(world view).
Key Methods:
add_vector_layer(name, selectable=True)- Create a vector layer for points, polygons, circles, ellipsesadd_fast_points_layer(name, selectable, style, cell_size_m)- Create a high-performance points layeradd_fast_geopoints_layer(name, selectable, style, cell_size_m)- Create a geo-points layer with uncertainty ellipsesadd_wms(options, name)- Add a WMS (Web Map Service) layeradd_raster_image(image, bounds, style, name)- Add a raster image overlayset_base_opacity(opacity)- Set OSM base layer opacity (0.0-1.0)set_measure_mode(enabled)- Enable/disable interactive distance measurement modeclear_measurements()- Clear all measurement points and linesget_view_extent(callback)- Get current map extent asynchronouslywatch_view_extent(callback, debounce_ms)- Subscribe to extent changes
Signals:
ready- Emitted when the map is readyselectionChanged- Emitted when feature selection changesviewExtentChanged- Emitted when map extent changes
Layer Types
VectorLayer
For standard vector features with full styling control.
from pyopenlayersqt import PointStyle, PolygonStyle, CircleStyle, EllipseStyle
# Add a vector layer
vector = map_widget.add_vector_layer("vector", selectable=True)
# Add points
vector.add_points(
coords=[(lat, lon), ...],
ids=["id1", "id2", ...],
style=PointStyle(
radius=6.0,
fill_color="#ff3333",
fill_opacity=0.85,
stroke_color="#000000",
stroke_width=1.0
)
)
# Add polygons
vector.add_polygon(
ring=[(lat1, lon1), (lat2, lon2), ...],
feature_id="poly1",
style=PolygonStyle(
stroke_color="#00aaff",
stroke_width=2.0,
fill_color="#00aaff",
fill_opacity=0.15
)
)
# Add lines (polylines)
vector.add_line(
coords=[(lat1, lon1), (lat2, lon2), (lat3, lon3)],
feature_id="ln1",
style=PolygonStyle(
stroke_color="#00aaff",
stroke_width=2.0
)
)
# Add circles (radius in meters)
vector.add_circle(
center=(lat, lon),
radius_m=1000.0,
feature_id="circle1",
style=CircleStyle(stroke_color="#00aaff", fill_opacity=0.15)
)
# Add ellipses (semi-major/minor axes in meters, tilt in degrees from north)
vector.add_ellipse(
center=(lat, lon),
sma_m=2000.0, # Semi-major axis
smi_m=1200.0, # Semi-minor axis
tilt_deg=45.0, # Tilt from true north
feature_id="ell1",
style=EllipseStyle(stroke_color="#ffcc00", fill_opacity=0.12)
)
# Update styles of specific features (e.g., selected features)
feature_ids = ["id1", "id2"]
new_styles = [
PointStyle(radius=8.0, fill_color="#ff0000", fill_opacity=1.0),
PointStyle(radius=8.0, fill_color="#00ff00", fill_opacity=1.0),
]
vector.update_feature_styles(feature_ids, new_styles)
# Remove features
vector.remove_features(["id1", "poly1"])
# Clear all features
vector.clear()
FastPointsLayer
High-performance layer for rendering millions of points using canvas and spatial indexing.
from pyopenlayersqt import FastPointsStyle
# Create fast points layer
fast = map_widget.add_fast_points_layer(
"fast_points",
selectable=True,
style=FastPointsStyle(
radius=2.5,
default_rgba=(0, 180, 0, 180), # RGBA 0-255
selected_radius=6.0,
selected_rgba=(255, 255, 0, 255)
),
cell_size_m=750.0 # Spatial index cell size
)
# Add points (efficient for large datasets)
coords = [(lat, lon), ...] # millions of points
ids = [f"pt{i}" for i in range(len(coords))]
# Option 1: Single color for all points
fast.add_points(coords, ids=ids)
# Option 2: Per-point colors
colors = [(r, g, b, a), ...] # RGBA tuples
fast.add_points(coords, ids=ids, colors_rgba=colors)
# Remove specific points
fast.remove_points(["pt1", "pt2"])
# Update colors of specific points (e.g., selected points)
feature_ids = ["pt10", "pt25", "pt50"]
new_colors = [(255, 0, 0, 255), (0, 255, 0, 255), (0, 0, 255, 255)]
fast.set_colors(feature_ids, new_colors)
# Clear all points
fast.clear()
FastGeoPointsLayer
High-performance layer for geolocation data with uncertainty ellipses.
from pyopenlayersqt import FastGeoPointsStyle
# Create fast geo points layer
fast_geo = map_widget.add_fast_geopoints_layer(
"fast_geo",
selectable=True,
style=FastGeoPointsStyle(
# Point styling
point_radius=2.5,
default_point_rgba=(40, 80, 255, 180),
selected_point_radius=6.0,
selected_point_rgba=(255, 255, 255, 255),
# Ellipse styling
ellipse_stroke_rgba=(40, 80, 255, 160),
ellipse_stroke_width=1.2,
fill_ellipses=False,
ellipse_fill_rgba=(40, 80, 255, 40),
# Behavior
ellipses_visible=True,
min_ellipse_px=0.0, # Cull tiny ellipses
max_ellipses_per_path=2000,
skip_ellipses_while_interacting=True
),
cell_size_m=750.0
)
# Add points with uncertainty ellipses
coords = [(lat, lon), ...]
sma_m = [200.0, 300.0, ...] # Semi-major axes in meters
smi_m = [100.0, 150.0, ...] # Semi-minor axes in meters
tilt_deg = [45.0, 90.0, ...] # Tilt from north in degrees
ids = [f"geo{i}" for i in range(len(coords))]
fast_geo.add_points_with_ellipses(
coords=coords,
sma_m=sma_m,
smi_m=smi_m,
tilt_deg=tilt_deg,
ids=ids
)
# Toggle ellipse visibility
fast_geo.set_ellipses_visible(False)
# Update colors of specific points (e.g., selected points)
feature_ids = ["geo5", "geo12", "geo20"]
new_colors = [(255, 0, 0, 255), (0, 255, 0, 255), (0, 0, 255, 255)]
fast_geo.set_colors(feature_ids, new_colors)
# Remove points
fast_geo.remove_ids(["geo1", "geo2"])
# Clear all
fast_geo.clear()
WMSLayer
Web Map Service layer integration.
from pyopenlayersqt import WMSOptions
# Add WMS layer
wms_options = WMSOptions(
url="https://ahocevar.com/geoserver/wms",
params={
"LAYERS": "topp:states",
"TILED": True,
"FORMAT": "image/png",
"TRANSPARENT": True
},
opacity=0.85
)
wms_layer = map_widget.add_wms(wms_options, name="wms")
# Update WMS parameters
wms_layer.set_params({"LAYERS": "new:layer"})
# Set opacity
wms_layer.set_opacity(0.5)
# Remove layer
wms_layer.remove()
RasterLayer
Image overlay layer for heatmaps, imagery, etc.
from pyopenlayersqt import RasterStyle
# Create PNG bytes (example using PIL)
from PIL import Image
import io
img = Image.new('RGBA', (512, 512), color=(255, 0, 0, 128))
buf = io.BytesIO()
img.save(buf, format='PNG')
png_bytes = buf.getvalue()
# Add raster overlay
bounds = [
(lat_min, lon_min), # Southwest corner
(lat_max, lon_max) # Northeast corner
]
raster = map_widget.add_raster_image(
png_bytes, # Can be bytes, file path, or URL
bounds=bounds,
style=RasterStyle(opacity=0.6),
name="heatmap"
)
# Update opacity
raster.set_opacity(0.8)
# Remove layer
raster.remove()
Style Classes
All style classes are immutable dataclasses with sensible defaults:
from pyopenlayersqt import (
PointStyle,
PolygonStyle,
CircleStyle,
EllipseStyle,
RasterStyle,
FastPointsStyle,
FastGeoPointsStyle
)
# Vector styles use CSS colors
point_style = PointStyle(
radius=5.0,
fill_color="#ff3333", # CSS color or (r,g,b) tuple
fill_opacity=0.85,
stroke_color="#000000",
stroke_width=1.0,
stroke_opacity=0.9
)
# Fast layer styles use RGBA tuples (0-255)
fast_style = FastPointsStyle(
radius=3.0,
default_rgba=(255, 51, 51, 204),
selected_radius=6.0,
selected_rgba=(0, 255, 255, 255)
)
Feature Selection
Selection is synchronized between the map and Python:
# Set selection programmatically
map_widget.set_vector_selection(layer_id, ["feature1", "feature2"])
map_widget.set_fast_points_selection(layer_id, ["pt1", "pt2"])
map_widget.set_fast_geopoints_selection(layer_id, ["geo1", "geo2"])
# Listen to selection changes from map
def on_selection_changed(selection):
print(f"Layer: {selection.layer_id}")
print(f"Selected IDs: {selection.feature_ids}")
print(f"Count: {selection.count}")
map_widget.selectionChanged.connect(on_selection_changed)
Selection and Recoloring
Update colors or styles of selected features across all layer types:
# For VectorLayer: Update feature styles
selected_ids = ["pt1", "pt2", "pt3"]
new_styles = [
PointStyle(radius=10.0, fill_color="#ff0000"),
PointStyle(radius=10.0, fill_color="#00ff00"),
PointStyle(radius=10.0, fill_color="#0000ff"),
]
vector_layer.update_feature_styles(selected_ids, new_styles)
# For FastPointsLayer: Update colors
selected_ids = ["fp1", "fp2", "fp3"]
new_colors = [(255, 0, 0, 255), (0, 255, 0, 255), (0, 0, 255, 255)]
fast_layer.set_colors(selected_ids, new_colors)
# For FastGeoPointsLayer: Update colors
selected_ids = ["geo1", "geo2", "geo3"]
new_colors = [(255, 0, 0, 255), (0, 255, 0, 255), (0, 0, 255, 255)]
fast_geo_layer.set_colors(selected_ids, new_colors)
Complete workflow example with multi-layer selection support:
# Track selections for all layers (layer_id -> list of feature_ids)
selections = {}
def on_selection_changed(selection):
global selections
# Update selections for this layer
if len(selection.feature_ids) > 0:
selections[selection.layer_id] = selection.feature_ids
elif selection.layer_id in selections:
# Clear selection for this layer
del selections[selection.layer_id]
total = sum(len(ids) for ids in selections.values())
print(f"Total selected: {total} features across {len(selections)} layer(s)")
map_widget.selectionChanged.connect(on_selection_changed)
# Recolor all selected items across all layers
def recolor_selected_red():
for layer_id, feature_ids in selections.items():
if layer_id == vector_layer.id:
styles = [PointStyle(fill_color="#ff0000") for _ in feature_ids]
vector_layer.update_feature_styles(feature_ids, styles)
elif layer_id == fast_layer.id:
colors = [(255, 0, 0, 255) for _ in feature_ids]
fast_layer.set_colors(feature_ids, colors)
elif layer_id == fast_geo_layer.id:
colors = [(255, 0, 0, 255) for _ in feature_ids]
fast_geo_layer.set_colors(feature_ids, colors)
See examples/06_selection_recoloring.py for a complete interactive example.
Distance Measurement Mode
Interactive distance measurement with geodesic calculations:
import json
# Enable measurement mode
map_widget.set_measure_mode(True)
# Listen for measurement events
def on_js_event(event_type, payload_json):
if event_type == 'measurement':
data = json.loads(payload_json)
segment_m = data['segment_distance_m'] # Distance from previous point
cumulative_m = data['cumulative_distance_m'] # Total distance from start
lon, lat = data['lon'], data['lat']
print(f"Point at ({lat:.5f}, {lon:.5f})")
print(f"Segment: {segment_m:.1f} m, Total: {cumulative_m:.1f} m")
map_widget.jsEvent.connect(on_js_event)
# Clear all measurements
map_widget.clear_measurements()
# Disable measurement mode
map_widget.set_measure_mode(False)
Features:
- Click on map to create measurement anchor points
- Live polyline drawn from last point to cursor
- Tooltip displays segment and cumulative distances
- Uses Haversine formula for accurate great-circle distances
- Lines follow great-circle paths - measurement lines curve to represent the true shortest path on Earth's surface
- Curved paths are especially visible for long distances (e.g., New York to London)
- Press
Escapeto exit measurement mode - Measurement events emitted to Python with distances and coordinates
See examples/03_measurement_mode.py for a complete working example.
FeatureTableWidget
High-performance table widget for displaying and managing features:
from pyopenlayersqt.features_table import FeatureTableWidget, ColumnSpec
# Define columns
columns = [
ColumnSpec("Layer", lambda r: r.get("layer_kind", "")),
ColumnSpec("Type", lambda r: r.get("geom_type", "")),
ColumnSpec("ID", lambda r: r.get("feature_id", "")),
ColumnSpec(
"Latitude",
lambda r: r.get("center_lat", ""),
fmt=lambda v: f"{float(v):.6f}" if v != "" else ""
),
ColumnSpec(
"Longitude",
lambda r: r.get("center_lon", ""),
fmt=lambda v: f"{float(v):.6f}" if v != "" else ""
),
]
# Create table
table = FeatureTableWidget(
columns=columns,
key_fn=lambda r: (str(r.get("layer_id", "")), str(r.get("feature_id", ""))),
debounce_ms=90
)
# Add rows
rows = [
{
"layer_kind": "vector",
"layer_id": "v1",
"feature_id": "pt1",
"geom_type": "point",
"center_lat": 37.7749,
"center_lon": -122.4194
}
]
table.append_rows(rows)
# Sync selection: table -> map
def on_table_selection(keys):
# keys is list of (layer_id, feature_id) tuples
for layer_id, feature_id in keys:
# Update map selection based on layer type
pass
table.selectionKeysChanged.connect(on_table_selection)
# Sync selection: map -> table
def on_map_selection(selection):
keys = [(selection.layer_id, fid) for fid in selection.feature_ids]
table.select_keys(keys, clear_first=True)
map_widget.selectionChanged.connect(on_map_selection)
RangeSliderWidget
Dual-handle range slider for filtering features by numeric or timestamp ranges:
from pyopenlayersqt.range_slider import RangeSliderWidget
# Numeric range slider
value_slider = RangeSliderWidget(
min_val=0.0,
max_val=100.0,
step=1.0,
label="Filter by Value"
)
# Connect to filter function
def on_value_range_changed(min_val, max_val):
# Filter features based on value range
visible_ids = [f["id"] for f in features if min_val <= f["value"] <= max_val]
hidden_ids = [f["id"] for f in features if not (min_val <= f["value"] <= max_val)]
# Hide/show features on map
layer.hide_features(hidden_ids)
layer.show_features(visible_ids)
# Hide/show rows in table
table.hide_rows_by_keys([(layer_id, fid) for fid in hidden_ids])
table.show_rows_by_keys([(layer_id, fid) for fid in visible_ids])
value_slider.rangeChanged.connect(on_value_range_changed)
# ISO8601 timestamp range slider
timestamps = ["2024-01-01T00:00:00Z", "2024-01-15T12:00:00Z", "2024-01-31T23:59:59Z"]
timestamp_slider = RangeSliderWidget(
values=sorted(set(timestamps)), # Unique sorted timestamps
label="Filter by Timestamp"
)
timestamp_slider.rangeChanged.connect(on_timestamp_range_changed)
# Reset filters
layer.show_all_features() # Show all on map
table.show_all_rows() # Show all in table
See examples/05_range_slider_filter.py for a complete working example with map and table filtering.
Complete Example
Here's a complete example based on the demo application. For a working version, see examples/02_complete_example.py.
from PySide6 import QtWidgets
from pyopenlayersqt import (
OLMapWidget,
PointStyle,
FastPointsStyle,
)
from pyopenlayersqt.features_table import FeatureTableWidget, ColumnSpec
import sys
import numpy as np
class MapWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("pyopenlayersqt Example")
# Create map widget centered on US West Coast at appropriate zoom
self.map_widget = OLMapWidget(center=(37.0, -120.0), zoom=6)
# Add layers
self.vector = self.map_widget.add_vector_layer("vector", selectable=True)
self.fast = self.map_widget.add_fast_points_layer(
"fast_points",
selectable=True,
style=FastPointsStyle(
radius=2.5,
default_rgba=(0, 180, 0, 180)
)
)
# Create feature table
columns = [
ColumnSpec("Layer", lambda r: r.get("layer_kind", "")),
ColumnSpec("Type", lambda r: r.get("geom_type", "")),
ColumnSpec("ID", lambda r: r.get("feature_id", "")),
]
self.table = FeatureTableWidget(
columns=columns,
key_fn=lambda r: (str(r.get("layer_id")), str(r.get("feature_id")))
)
# Connect signals
self.map_widget.selectionChanged.connect(self.on_map_selection)
self.table.selectionKeysChanged.connect(self.on_table_selection)
# Layout
container = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(container)
layout.addWidget(self.table, 1)
layout.addWidget(self.map_widget, 2)
self.setCentralWidget(container)
# Add data after map is ready
self.map_widget.ready.connect(self.add_sample_data)
def add_sample_data(self):
# Add a vector point
self.vector.add_points(
[(37.7749, -122.4194)],
ids=["sf"],
style=PointStyle(radius=8.0, fill_color="#ff3333")
)
# Add to table
self.table.append_rows([{
"layer_kind": "vector",
"layer_id": self.vector.id,
"feature_id": "sf",
"geom_type": "point"
}])
# Add fast points
rng = np.random.default_rng()
n = 10000
lats = 32 + rng.random(n) * 10
lons = -125 + rng.random(n) * 10
coords = list(zip(lats.tolist(), lons.tolist()))
ids = [f"fp{i}" for i in range(n)]
self.fast.add_points(coords, ids=ids)
# Add fast points to table
rows = (
{
"layer_kind": "fast_points",
"layer_id": self.fast.id,
"feature_id": ids[i],
"geom_type": "point"
}
for i in range(n)
)
self.table.append_rows(rows)
def on_map_selection(self, selection):
keys = [(selection.layer_id, fid) for fid in selection.feature_ids]
self.table.select_keys(keys, clear_first=True)
def on_table_selection(self, keys):
# Group by layer
by_layer = {}
for layer_id, fid in keys:
by_layer.setdefault(layer_id, []).append(fid)
# Update each layer's selection
for layer_id, fids in by_layer.items():
if layer_id == self.vector.id:
self.map_widget.set_vector_selection(layer_id, fids)
elif layer_id == self.fast.id:
self.map_widget.set_fast_points_selection(layer_id, fids)
def main():
app = QtWidgets.QApplication(sys.argv)
window = MapWindow()
window.resize(1200, 800)
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
Running the Demo
The repository includes a comprehensive demo application:
python demo/demo.py
The demo showcases:
- Vector layers with points, polygons, circles, and ellipses
- Fast points rendering (up to millions of points)
- Fast geo-points with uncertainty ellipses
- WMS layer integration
- Raster/heatmap overlays with custom rendering
- Feature table with bidirectional selection sync
- Dynamic styling and opacity controls
View Extent Tracking
Monitor map extent changes for dynamic data loading:
# One-time extent request
def on_extent(extent):
print(f"Extent: {extent['lon_min']}, {extent['lat_min']} to "
f"{extent['lon_max']}, {extent['lat_max']}")
print(f"Zoom: {extent['zoom']}, Resolution: {extent['resolution']}")
map_widget.get_view_extent(on_extent)
# Watch extent changes (debounced)
def on_extent_changed(extent):
# Load data for current extent
load_data_for_extent(extent)
handle = map_widget.watch_view_extent(on_extent_changed, debounce_ms=150)
# Stop watching
handle.cancel()
Advanced: Direct JavaScript Communication
For advanced use cases, you can send custom messages to the JavaScript bridge:
# Send custom message to JavaScript
map_widget.send({
"type": "custom_command",
"param1": "value1",
"param2": 123
})
# Listen to JavaScript events
def on_js_event(event_type, payload_json):
print(f"Event: {event_type}, Payload: {payload_json}")
map_widget.jsEvent.connect(on_js_event)
Performance Tips
- Use Fast Layers for Large Datasets: For > 1000 points, use
FastPointsLayerorFastGeoPointsLayerinstead of vector layers - Tune Cell Size: Adjust
cell_size_mparameter based on your data density (larger = faster, but less precise selection) - Chunk Large Additions:
FastGeoPointsLayer.add_points_with_ellipses()automatically chunks data (default 50k points per chunk) - Debounce Extent Watching: Use appropriate
debounce_mswhen watching extent changes to avoid excessive updates - Cull Tiny Ellipses: Set
min_ellipse_pxinFastGeoPointsStyleto skip rendering very small ellipses - Skip Ellipses While Interacting: Enable
skip_ellipses_while_interactingfor smoother panning/zooming
Architecture
- Python → JavaScript: Commands sent via
window.pyolqt_send() - JavaScript → Python: Events sent via Qt Web Channel (
qtBridge.emitEvent()) - Static Assets: Served by embedded HTTP server (wheel-safe)
- Raster Overlays: Written to user cache directory and served dynamically
License
MIT License
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
Versioning and Releases
This project uses Semantic Versioning for version numbers (MAJOR.MINOR.PATCH).
For Maintainers: Creating a Release
1. Update the Version
Update the version number in pyproject.toml:
[project]
version = "X.Y.Z" # e.g., "0.2.0"
2. Commit the Version Change
git add pyproject.toml
git commit -m "Bump version to X.Y.Z"
git push origin main
3. Create and Push a Git Tag
Create a tag matching the version number (with a v prefix):
git tag vX.Y.Z # e.g., v0.2.0
git push origin vX.Y.Z
For pre-release versions, use a suffix:
git tag vX.Y.Z-alpha.1 # e.g., v0.2.0-alpha.1
git tag vX.Y.Z-beta.1 # e.g., v0.2.0-beta.1
git tag vX.Y.Z-rc.1 # e.g., v0.2.0-rc.1
git push origin vX.Y.Z-alpha.1
4. Automated Publishing
Once the tag is pushed, GitHub Actions will automatically:
- Build the package using PEP 517 (
python -m build) - Publish to PyPI using trusted publishing (OIDC)
You can monitor the workflow at: https://github.com/crroush/pyopenlayersqt/actions
5. Manual Workflow Trigger
You can also trigger the publish workflow manually from the GitHub Actions tab:
- Go to https://github.com/crroush/pyopenlayersqt/actions
- Select the "Publish to PyPI" workflow
- Click "Run workflow"
- Select the branch/tag to build from
PyPI Setup Requirements
This project uses PyPI Trusted Publishing (OIDC), which is more secure than using API tokens.
Initial Setup (One-Time)
-
Create a PyPI Account (if you don't have one):
-
Configure Trusted Publisher on PyPI:
- Go to https://pypi.org/manage/account/publishing/
- Add a new pending publisher with these details:
- PyPI Project Name:
pyopenlayersqt - Owner:
crroush - Repository name:
pyopenlayersqt - Workflow name:
publish.yml - Environment name:
pypi
- PyPI Project Name:
-
After First Successful Publish:
- The pending publisher will be automatically converted to an active publisher
- Future releases will publish automatically when you push a tag
Alternative: Using API Tokens
If trusted publishing is not available, you can use API tokens instead:
- Generate a PyPI API token at https://pypi.org/manage/account/token/
- Add it as a GitHub repository secret named
PYPI_API_TOKEN - Update the workflow to use token-based authentication (see commented section in
.github/workflows/publish.yml)
Verification
After a release is published, verify it at:
- PyPI: https://pypi.org/project/pyopenlayersqt/
- Test installation:
pip install --upgrade pyopenlayersqt
Credits
Built with:
- OpenLayers - High-performance web mapping library
- PySide6 - Qt for Python
- NumPy - Numerical computing
- Matplotlib - Plotting and colormaps
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyopenlayersqt-0.1.0.tar.gz.
File metadata
- Download URL: pyopenlayersqt-0.1.0.tar.gz
- Upload date:
- Size: 289.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3a0d780e37d9c46bf04ecd11f5952b42377d68bc46de2d5dd531774a58be7f9e
|
|
| MD5 |
2808d20b5d8e890d5dfc382a64c6ae37
|
|
| BLAKE2b-256 |
97091c4ab1574ec32f0f5ad00b8e400b1f847c7afce6a7ea3292f73322606a8f
|
Provenance
The following attestation bundles were made for pyopenlayersqt-0.1.0.tar.gz:
Publisher:
publish.yml on crroush/pyopenlayersqt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyopenlayersqt-0.1.0.tar.gz -
Subject digest:
3a0d780e37d9c46bf04ecd11f5952b42377d68bc46de2d5dd531774a58be7f9e - Sigstore transparency entry: 850024269
- Sigstore integration time:
-
Permalink:
crroush/pyopenlayersqt@370cddfc9f29ac9879eae4cb6734343f99479da4 -
Branch / Tag:
refs/tags/v0.1.0-alpha.1 - Owner: https://github.com/crroush
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@370cddfc9f29ac9879eae4cb6734343f99479da4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyopenlayersqt-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyopenlayersqt-0.1.0-py3-none-any.whl
- Upload date:
- Size: 283.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd3f45327fd2afe117cd4f791260922b5b25a6fa38c0be6832422464640b0570
|
|
| MD5 |
ad4b96429cf8c17e0ac3298215536ea9
|
|
| BLAKE2b-256 |
6f7b5ede228da7ae33d8a2447c08d83c340ca9f96e24c5db7d27d4a911a6ddf6
|
Provenance
The following attestation bundles were made for pyopenlayersqt-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on crroush/pyopenlayersqt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyopenlayersqt-0.1.0-py3-none-any.whl -
Subject digest:
cd3f45327fd2afe117cd4f791260922b5b25a6fa38c0be6832422464640b0570 - Sigstore transparency entry: 850024270
- Sigstore integration time:
-
Permalink:
crroush/pyopenlayersqt@370cddfc9f29ac9879eae4cb6734343f99479da4 -
Branch / Tag:
refs/tags/v0.1.0-alpha.1 - Owner: https://github.com/crroush
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@370cddfc9f29ac9879eae4cb6734343f99479da4 -
Trigger Event:
push
-
Statement type: