gispulse 2.2.1__tar.gz → 2.2.2__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.
- {gispulse-2.2.1/src/gispulse.egg-info → gispulse-2.2.2}/PKG-INFO +1 -1
- {gispulse-2.2.1 → gispulse-2.2.2}/pyproject.toml +1 -1
- gispulse-2.2.2/src/gispulse/capabilities/vector/snap_points.py +259 -0
- {gispulse-2.2.1 → gispulse-2.2.2/src/gispulse.egg-info}/PKG-INFO +1 -1
- gispulse-2.2.1/src/gispulse/capabilities/vector/snap_points.py +0 -185
- {gispulse-2.2.1 → gispulse-2.2.2}/LICENSE +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/LICENSE-COMMERCIAL.md +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/README.md +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/setup.cfg +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/_compat.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/_pyogrio_warnings.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/apicarto.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/action_dispatcher.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/bus_message.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/circuit_breaker.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/dlq.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/enums.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/event_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/pg_notify.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/pool.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/predicate_evaluator.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/state_store.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/trigger_manager.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/workers/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/workers/base_worker.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/workers/dispatch_worker.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/workers/identify_worker.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/app.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/auth.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/dataset_ops.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/dependencies.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/error_handlers.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/event_hub.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/layer_utils.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/middleware/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/middleware/audit_middleware.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/middleware/metrics_middleware.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/middleware/read_only.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/portal_app.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/rate_limit.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/_upload_utils.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/auth_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/capabilities_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/catalog_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/datasets_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/esb_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/examples_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/filter_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/jobs_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/marketplace_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/ogc_features_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/pipelines_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_datasets_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_features_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_sql_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_upload_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/projects_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/relations_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/rules_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/scenarios_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/schedules_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/sessions_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/system_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/templates_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/tiles_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/triggers_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/viewer_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/watchers_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/ws_router.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/schemas.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/serve_app.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/mcp/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/mcp/dryrun.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/mcp/server.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/mcp/workdir.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/metrics.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/ogc/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/ogc/auth.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/ogc/loader.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/ogc/wfs_client.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/ogc/wfs_fetcher.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/rest/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/rest/rest_fetcher.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/rest/rest_table_fetcher.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/stac/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/stac/stac_fetcher.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/webhooks/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/webhooks/http_client.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/app.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/_attribute_sql.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/_geometry_sql.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/base.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/classification.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/clustering.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/density.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/network.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/network_components.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/network_graph.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/network_topology.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/overlay.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/palettes.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/pointcloud.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/polygon_topology.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/postgis_sql.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/raster.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/registry.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/relation_detector.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/schema.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/selection.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/spatial_stats.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/sql_pushdown.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/strategy.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/temporal.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/transforms.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/validation.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/aggregate.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/assign_projection.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/boundary.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/buffer.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/calculate.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/centroid_area.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/chaikin.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/classify.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/clip.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/concave_hull.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/diff.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/dissolve.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/extract_holes.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/extract_ops.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/filter.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/force_geometry_type.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/intersects.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/line_merge.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/line_ops.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/merge.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/nearest.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/offset_curve.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/parts.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/polygonize.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/reproject.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/shape_ops_advanced.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/shape_ops_basic.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/simplify.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/snap_grid.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/spatial_join.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/split_lines.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/union.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/voronoi.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/data/basemaps.json +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/data/epsg_common.json +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/models.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/base.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/basemaps.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/flux_ign.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/flux_osm.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/opendata_datagouv.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/opendata_hub.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/opendata_ign.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/projections.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/stac_client.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/registry.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/source_bridge.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_mcp.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_portal.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_track.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_triggers.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_triggers_watch.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_watch.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/config.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/assertions.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/bulk_ingest.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/bulk_runner.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/cache.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/capability_params.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/conditions.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/config.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/crs.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/dag.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/data/worldwide_catalog.yml +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/data_pack_signature.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/dispatcher.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/enums.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/explain.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/base.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/geoparquet_s3.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/http_file.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/ogc_client.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/ogc_features.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/stac.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/table_file.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/cache.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/chain.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/expression.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/expression_converter.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/result.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/service.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/types.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/graph.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/io/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/io/geoparquet.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/licence_format.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/logging.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/manifest_v3.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/models.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/network_graph_handle.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/observability.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/pipeline.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/pipeline_schema.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/plugin_contracts.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/plugin_hub.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/plugin_model.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/predicates.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/pricing_catalog.yml +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/registry.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/regulatory_zoning_entry.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/relations.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/session.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/sources.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/spatial_index.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/sql_safety.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/ssrf.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/zoning_normalizer.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/diagnostics/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/diagnostics/system.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/dsl/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/dsl/expression_parser.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/dsl/geom_fcts.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/capability_executor.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/graph_executor.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/job_queue.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/job_queue_factory.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/metering.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/pipeline_executor.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/runner.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/scenario_runner.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/scheduler.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/session_manager.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/trigger_bridge.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/worker.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/audit.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/auth_models.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/auth_repository.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/bridge.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/change_log_watcher.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/changelog_doctor.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/changelog_reader.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/datamart.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/duckdb_change_detector.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/duckdb_diff_engine.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/duckdb_engine.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/duckdb_engine_adapter.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/duckdb_relations.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/engine.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/engine_factory.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/file_blob_cdc.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/geonode.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg_connection.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg_engine.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg_repository.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg_schema.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg_spatial.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/io.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/licence.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/loader.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/postgis.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/project_io.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/raster_io.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/repository.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/schedule_repository.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/schema.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/session_provisioner.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/sld_converter.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/source_watcher.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/spatial_queries.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/spatialite_engine.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/spatialite_session.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/sql_dialect.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/sql_guardrails.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/sqlite_repository.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/storage.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/style_converter.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/style_sidecar.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/tier.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/virtual_dataset.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/watcher_registry.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/api.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/datagouv_refresh.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/mcp_pilot.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/pipeline.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/sources.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/spatial.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/worldwide_source.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/engine.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/loader.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/operation_executor.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/predicates.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/trigger_evaluator.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/validation.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/__init__.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/config_loader.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/dialect_scanner.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/duckdb_engine.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/engine_inference.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/headless_runtime.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/layer_registry.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/manifest_runner.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/predicate_dsl.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/source_watch.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/sqlite_retry.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/validation_runner.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/telemetry.py +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse.egg-info/SOURCES.txt +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse.egg-info/dependency_links.txt +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse.egg-info/entry_points.txt +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse.egg-info/requires.txt +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse.egg-info/top_level.txt +0 -0
- {gispulse-2.2.1 → gispulse-2.2.2}/tests/test_read_only_middleware.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gispulse
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Modular geospatial engine with business rules, triggers, and dual operating modes (DuckDB/PostGIS)
|
|
5
5
|
Author-email: ImagoData <contact@imagodata.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gispulse"
|
|
7
|
-
version = "2.2.
|
|
7
|
+
version = "2.2.2"
|
|
8
8
|
description = "Modular geospatial engine with business rules, triggers, and dual operating modes (DuckDB/PostGIS)"
|
|
9
9
|
license = "AGPL-3.0-or-later"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Snap points to a line network with stable edge ids (capability B).
|
|
2
|
+
|
|
3
|
+
Where ``line_locate_point`` returns only a measure plus a *positional*
|
|
4
|
+
``ref_index``, this capability returns everything needed to attach an event
|
|
5
|
+
to a routable edge:
|
|
6
|
+
|
|
7
|
+
* ``edge_id`` — the matched line's id (from ``ref_id_col``), stable
|
|
8
|
+
across runs (not a row position);
|
|
9
|
+
* ``measure`` — distance along the matched line, in meters, from its
|
|
10
|
+
start (in ``[0, line_length]``); null when unsnapped;
|
|
11
|
+
* ``offset_distance`` — perpendicular distance from the point to its nearest
|
|
12
|
+
line (always reported, snapped or not);
|
|
13
|
+
* ``snapped`` — ``True`` when ``offset_distance <= max_distance_m``
|
|
14
|
+
(or ``max_distance_m is None``), ``False`` otherwise;
|
|
15
|
+
* ``geometry`` — for snapped rows, the **projected** point on the
|
|
16
|
+
nearest line; for unsnapped rows the original point is kept.
|
|
17
|
+
|
|
18
|
+
A point beyond ``max_distance_m`` is reported with ``snapped=False``,
|
|
19
|
+
``edge_id``/``measure`` null and its original geometry — only
|
|
20
|
+
``offset_distance`` records how far its nearest line lies, so the consumer
|
|
21
|
+
can decide what to do. A null/empty input geometry has no nearest line and
|
|
22
|
+
stays ``edge_id=None``, ``snapped=False`` with its geometry untouched.
|
|
23
|
+
|
|
24
|
+
Candidate lines are found through a :class:`~gispulse.core.spatial_index.SpatialIndex`
|
|
25
|
+
(STRtree, no O(n·m) scan); each point projects directly onto its segment.
|
|
26
|
+
Ties — several lines exactly equidistant from one point — break
|
|
27
|
+
deterministically on the smallest ``edge_id`` so the same inputs always
|
|
28
|
+
produce the same outputs.
|
|
29
|
+
|
|
30
|
+
All metric quantities (``measure``, ``offset_distance``, ``max_distance_m``)
|
|
31
|
+
are computed in ``crs_meters`` (a projected CRS); inputs in another CRS are
|
|
32
|
+
reprojected in and the result is reprojected back to the points' original
|
|
33
|
+
CRS. ``crs_meters`` must be a metric/projected CRS — never feed it degrees.
|
|
34
|
+
|
|
35
|
+
This capability is deliberately **generic**: it carries no business notion
|
|
36
|
+
(site, fibre edge, river reach…). Accidents on a road network, meters on a
|
|
37
|
+
utility line and discharges on a watercourse are all the same operation —
|
|
38
|
+
project points onto identified lines — and none is special-cased here.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import geopandas as gpd
|
|
44
|
+
import numpy as np
|
|
45
|
+
|
|
46
|
+
from gispulse.capabilities.base import Capability
|
|
47
|
+
from gispulse.capabilities.registry import register
|
|
48
|
+
from gispulse.core.spatial_index import SpatialIndex
|
|
49
|
+
|
|
50
|
+
# How many nearest lines to inspect per point when breaking ties. Genuine
|
|
51
|
+
# geometric ties (several lines exactly equidistant from one point) are rare;
|
|
52
|
+
# 8 candidates is ample headroom while keeping each lookup O(k·log n) — far
|
|
53
|
+
# cheaper (and bounded) than a radius query whose buffer could match the whole
|
|
54
|
+
# network for a far-off point.
|
|
55
|
+
_TIE_CANDIDATES = 8
|
|
56
|
+
# Distances within this many CRS units (meters) are treated as equal for the
|
|
57
|
+
# purpose of tie-breaking on edge_id.
|
|
58
|
+
_TIE_TOL_M = 1e-9
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _id_sort_key(value: object) -> "tuple[int, object]":
|
|
62
|
+
"""Deterministic, total ordering key for tie-breaking on edge ids.
|
|
63
|
+
|
|
64
|
+
Real ids in a single ``ref_id_col`` are homogeneous (all ints or all
|
|
65
|
+
strings), where this reduces to natural order. The two-level key only
|
|
66
|
+
guards the pathological mixed-type column so ``min`` never raises and the
|
|
67
|
+
outcome stays reproducible.
|
|
68
|
+
"""
|
|
69
|
+
if isinstance(value, bool): # bool is an int subclass — keep it on the str side
|
|
70
|
+
return (1, str(value))
|
|
71
|
+
if isinstance(value, (int, float)):
|
|
72
|
+
return (0, value)
|
|
73
|
+
return (1, str(value))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@register
|
|
77
|
+
class SnapPointsToLinesCapability(Capability):
|
|
78
|
+
"""Projects points onto a line network, tagging each with its edge id."""
|
|
79
|
+
|
|
80
|
+
name = "snap_points_to_lines"
|
|
81
|
+
description = (
|
|
82
|
+
"Snaps each point to the nearest line, adding edge_id (from "
|
|
83
|
+
"ref_id_col), measure, offset_distance and a snapped flag; geometry "
|
|
84
|
+
"becomes the projected point on the nearest line."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def execute(
|
|
88
|
+
self,
|
|
89
|
+
gdf: gpd.GeoDataFrame,
|
|
90
|
+
ref_gdf: gpd.GeoDataFrame | None = None,
|
|
91
|
+
ref_id_col: str | None = None,
|
|
92
|
+
max_distance_m: float | None = None,
|
|
93
|
+
crs_meters: str = "EPSG:3857",
|
|
94
|
+
edge_id_col: str = "edge_id",
|
|
95
|
+
measure_col: str = "measure",
|
|
96
|
+
offset_col: str = "offset_distance",
|
|
97
|
+
snapped_col: str = "snapped",
|
|
98
|
+
**_,
|
|
99
|
+
) -> gpd.GeoDataFrame:
|
|
100
|
+
"""Project each point onto its nearest reference line.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
gdf: Input points (non-points use their centroid). All
|
|
104
|
+
input columns are preserved on the output.
|
|
105
|
+
ref_gdf: Reference line layer (injected via ``ref_layer``).
|
|
106
|
+
ref_id_col: Column on ``ref_gdf`` providing the stable
|
|
107
|
+
``edge_id``. **Required** — its absence raises a
|
|
108
|
+
clear ``ValueError`` rather than silently falling
|
|
109
|
+
back to a positional index.
|
|
110
|
+
max_distance_m: Snap threshold in meters. A point whose nearest
|
|
111
|
+
line is farther than this is left unsnapped:
|
|
112
|
+
``snapped=False``, ``edge_id``/``measure`` null
|
|
113
|
+
and the original geometry kept, with only
|
|
114
|
+
``offset_distance`` reporting the true nearest
|
|
115
|
+
distance. ``None`` snaps every point to its
|
|
116
|
+
nearest line.
|
|
117
|
+
crs_meters: Metric (projected) CRS used for all distances.
|
|
118
|
+
edge_id_col: Output column for the matched edge id.
|
|
119
|
+
measure_col: Output column for the along-line measure (m).
|
|
120
|
+
offset_col: Output column for the perpendicular offset (m).
|
|
121
|
+
snapped_col: Output boolean column.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Copy of ``gdf`` (input columns intact) with the four columns added
|
|
125
|
+
and ``geometry`` replaced by the projected point, in the input CRS.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
ValueError: if ``ref_gdf`` is missing/empty, or if ``ref_id_col``
|
|
129
|
+
is not a column of ``ref_gdf``.
|
|
130
|
+
"""
|
|
131
|
+
if ref_gdf is None or ref_gdf.empty:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
"snap_points_to_lines requires a non-empty reference line layer "
|
|
134
|
+
"(ref_gdf). Pass the lines to project the points onto."
|
|
135
|
+
)
|
|
136
|
+
if ref_id_col is None or ref_id_col not in ref_gdf.columns:
|
|
137
|
+
raise ValueError(
|
|
138
|
+
"snap_points_to_lines: ref_id_col="
|
|
139
|
+
f"{ref_id_col!r} is not a column of ref_gdf "
|
|
140
|
+
f"(available columns: {list(ref_gdf.columns)}). "
|
|
141
|
+
"Pass ref_id_col=<the stable id column on your lines> so every "
|
|
142
|
+
"snapped point can carry a durable edge_id."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
original_crs = gdf.crs
|
|
146
|
+
left = gdf.to_crs(crs_meters) if original_crs is not None else gdf.copy()
|
|
147
|
+
right = (
|
|
148
|
+
ref_gdf.to_crs(crs_meters)
|
|
149
|
+
if ref_gdf.crs is not None and str(ref_gdf.crs) != str(crs_meters)
|
|
150
|
+
else ref_gdf.copy()
|
|
151
|
+
)
|
|
152
|
+
right = right.reset_index(drop=True)
|
|
153
|
+
|
|
154
|
+
line_geoms = list(right.geometry)
|
|
155
|
+
ref_ids = list(right[ref_id_col])
|
|
156
|
+
index = SpatialIndex(line_geoms)
|
|
157
|
+
|
|
158
|
+
n = len(left)
|
|
159
|
+
edge_ids: list[object] = [None] * n
|
|
160
|
+
measures = np.full(n, np.nan, dtype=float)
|
|
161
|
+
offsets = np.full(n, np.nan, dtype=float)
|
|
162
|
+
snapped = np.zeros(n, dtype=bool)
|
|
163
|
+
new_geoms = list(left.geometry)
|
|
164
|
+
|
|
165
|
+
for row_i, geom in enumerate(left.geometry):
|
|
166
|
+
if geom is None or geom.is_empty:
|
|
167
|
+
continue
|
|
168
|
+
pt = geom if geom.geom_type == "Point" else geom.centroid
|
|
169
|
+
|
|
170
|
+
pos = self._nearest_line(index, line_geoms, ref_ids, pt)
|
|
171
|
+
if pos is None: # ref layer had no usable geometry
|
|
172
|
+
continue
|
|
173
|
+
line = line_geoms[pos]
|
|
174
|
+
offset = line.distance(pt)
|
|
175
|
+
offsets[row_i] = float(offset) # nearest distance is always reported
|
|
176
|
+
if max_distance_m is not None and offset > max_distance_m:
|
|
177
|
+
# Beyond the threshold: leave edge_id/measure null and the
|
|
178
|
+
# original geometry in place (snapped stays False).
|
|
179
|
+
continue
|
|
180
|
+
measure = line.project(pt)
|
|
181
|
+
measures[row_i] = float(measure)
|
|
182
|
+
edge_ids[row_i] = ref_ids[pos]
|
|
183
|
+
new_geoms[row_i] = line.interpolate(measure)
|
|
184
|
+
snapped[row_i] = True
|
|
185
|
+
|
|
186
|
+
out = left.copy()
|
|
187
|
+
out[edge_id_col] = edge_ids
|
|
188
|
+
out[measure_col] = measures
|
|
189
|
+
out[offset_col] = offsets
|
|
190
|
+
out[snapped_col] = snapped
|
|
191
|
+
out = out.set_geometry(
|
|
192
|
+
gpd.GeoSeries(new_geoms, index=out.index, crs=crs_meters)
|
|
193
|
+
)
|
|
194
|
+
if original_crs is not None:
|
|
195
|
+
out = out.to_crs(original_crs)
|
|
196
|
+
return out.reset_index(drop=True)
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def _nearest_line(
|
|
200
|
+
index: SpatialIndex,
|
|
201
|
+
line_geoms: list,
|
|
202
|
+
ref_ids: list,
|
|
203
|
+
pt,
|
|
204
|
+
) -> "int | None":
|
|
205
|
+
"""Position of the nearest line to ``pt``, ties broken by smallest id.
|
|
206
|
+
|
|
207
|
+
Inspects the ``_TIE_CANDIDATES`` nearest lines (O(k·log n)), keeps
|
|
208
|
+
those within ``_TIE_TOL_M`` of the minimum distance, and returns the
|
|
209
|
+
one with the smallest ``edge_id`` — a stable, deterministic choice.
|
|
210
|
+
"""
|
|
211
|
+
near = index.nearest(pt, k=min(_TIE_CANDIDATES, len(line_geoms)))
|
|
212
|
+
if not near:
|
|
213
|
+
return None
|
|
214
|
+
dists = {pos: line_geoms[pos].distance(pt) for pos in near}
|
|
215
|
+
best_dist = min(dists.values())
|
|
216
|
+
tied = [pos for pos, d in dists.items() if d <= best_dist + _TIE_TOL_M]
|
|
217
|
+
return min(tied, key=lambda pos: _id_sort_key(ref_ids[pos]))
|
|
218
|
+
|
|
219
|
+
def get_schema(self) -> dict:
|
|
220
|
+
return {
|
|
221
|
+
"type": "object",
|
|
222
|
+
"properties": {
|
|
223
|
+
"ref_layer": {
|
|
224
|
+
"type": "string",
|
|
225
|
+
"description": "Reference line layer to snap onto.",
|
|
226
|
+
},
|
|
227
|
+
"ref_id_col": {
|
|
228
|
+
"type": "string",
|
|
229
|
+
"description": (
|
|
230
|
+
"Required. Column on the lines providing the stable "
|
|
231
|
+
"edge_id; its absence raises a ValueError."
|
|
232
|
+
),
|
|
233
|
+
},
|
|
234
|
+
"max_distance_m": {
|
|
235
|
+
"type": ["number", "null"],
|
|
236
|
+
"description": (
|
|
237
|
+
"Snap threshold (m). Beyond it, snapped=False with "
|
|
238
|
+
"edge_id/measure null and the original geometry kept "
|
|
239
|
+
"(only offset_distance reported). None snaps every "
|
|
240
|
+
"point to its nearest line."
|
|
241
|
+
),
|
|
242
|
+
},
|
|
243
|
+
"crs_meters": {"type": "string", "default": "EPSG:3857"},
|
|
244
|
+
"edge_id_col": {"type": "string", "default": "edge_id"},
|
|
245
|
+
"measure_col": {"type": "string", "default": "measure"},
|
|
246
|
+
"offset_col": {"type": "string", "default": "offset_distance"},
|
|
247
|
+
"snapped_col": {"type": "string", "default": "snapped"},
|
|
248
|
+
},
|
|
249
|
+
# ``ref_id_col`` is genuinely required and is not plumbing, so it
|
|
250
|
+
# can validate early. ``ref_layer`` must NOT appear in ``required``:
|
|
251
|
+
# it is pipeline plumbing (resolved to ``ref_gdf`` and stripped
|
|
252
|
+
# before schema validation), so requiring it would fail every v2
|
|
253
|
+
# call before the capability runs. The runtime still raises a clear
|
|
254
|
+
# ValueError when ``ref_gdf`` itself is missing.
|
|
255
|
+
"required": ["ref_id_col"],
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
__all__ = ["SnapPointsToLinesCapability"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gispulse
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Modular geospatial engine with business rules, triggers, and dual operating modes (DuckDB/PostGIS)
|
|
5
5
|
Author-email: ImagoData <contact@imagodata.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
"""Snap points to a line network with stable edge ids (capability B).
|
|
2
|
-
|
|
3
|
-
Where ``line_locate_point`` returns only a measure plus a *positional*
|
|
4
|
-
``ref_index``, this capability returns everything needed to attach an event
|
|
5
|
-
to a routable edge:
|
|
6
|
-
|
|
7
|
-
* ``edge_id`` — the matched line's id (from ``ref_id_col``), stable
|
|
8
|
-
across runs (not a row position);
|
|
9
|
-
* ``measure`` — distance along the matched line, in meters;
|
|
10
|
-
* ``offset_distance`` — perpendicular distance from the point to the line;
|
|
11
|
-
* ``snapped`` — False when no line lies within ``max_distance_m``;
|
|
12
|
-
* ``geometry`` — replaced by the **projected** point on the line
|
|
13
|
-
(the original point is kept for unsnapped rows).
|
|
14
|
-
|
|
15
|
-
Candidate lines are found through a :class:`~gispulse.core.spatial_index.SpatialIndex`
|
|
16
|
-
(no O(n·m) scan). Processing happens in a metric CRS so ``measure`` /
|
|
17
|
-
``offset_distance`` / ``max_distance_m`` are in meters; the result is
|
|
18
|
-
reprojected to the input CRS.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
from __future__ import annotations
|
|
22
|
-
|
|
23
|
-
import geopandas as gpd
|
|
24
|
-
import numpy as np
|
|
25
|
-
|
|
26
|
-
from gispulse.capabilities.base import Capability
|
|
27
|
-
from gispulse.capabilities.registry import register
|
|
28
|
-
from gispulse.core.spatial_index import SpatialIndex
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@register
|
|
32
|
-
class SnapPointsToLinesCapability(Capability):
|
|
33
|
-
"""Projects points onto a line network, tagging each with its edge id."""
|
|
34
|
-
|
|
35
|
-
name = "snap_points_to_lines"
|
|
36
|
-
description = (
|
|
37
|
-
"Snaps each point to the nearest line within max_distance_m, adding "
|
|
38
|
-
"edge_id (from ref_id_col), measure, offset_distance and a snapped "
|
|
39
|
-
"flag; geometry becomes the projected point."
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
def execute(
|
|
43
|
-
self,
|
|
44
|
-
gdf: gpd.GeoDataFrame,
|
|
45
|
-
ref_gdf: gpd.GeoDataFrame | None = None,
|
|
46
|
-
ref_id_col: str | None = None,
|
|
47
|
-
max_distance_m: float | None = None,
|
|
48
|
-
edge_id_col: str = "edge_id",
|
|
49
|
-
measure_col: str = "measure",
|
|
50
|
-
offset_col: str = "offset_distance",
|
|
51
|
-
snapped_col: str = "snapped",
|
|
52
|
-
crs_meters: str = "EPSG:3857",
|
|
53
|
-
**_,
|
|
54
|
-
) -> gpd.GeoDataFrame:
|
|
55
|
-
"""
|
|
56
|
-
Args:
|
|
57
|
-
gdf: Input points (non-points use their centroid).
|
|
58
|
-
ref_gdf: Reference line layer (injected via ``ref_layer``).
|
|
59
|
-
ref_id_col: Column on ``ref_gdf`` providing ``edge_id``.
|
|
60
|
-
Defaults to the line's row position.
|
|
61
|
-
max_distance_m: Max snap distance in meters. Points with no line
|
|
62
|
-
within it are left unsnapped (``snapped=False``,
|
|
63
|
-
original geometry kept). ``None`` always snaps to
|
|
64
|
-
the nearest line.
|
|
65
|
-
edge_id_col: Output column for the matched edge id.
|
|
66
|
-
measure_col: Output column for the along-line measure (m).
|
|
67
|
-
offset_col: Output column for the perpendicular offset (m).
|
|
68
|
-
snapped_col: Output boolean column.
|
|
69
|
-
crs_meters: Metric CRS used for all distances.
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
Copy of ``gdf`` with the four columns added and ``geometry``
|
|
73
|
-
replaced by the projected point (snapped rows), in the input CRS.
|
|
74
|
-
"""
|
|
75
|
-
if ref_gdf is None or ref_gdf.empty:
|
|
76
|
-
raise ValueError("snap_points_to_lines requires a reference line layer.")
|
|
77
|
-
|
|
78
|
-
original_crs = gdf.crs
|
|
79
|
-
left = gdf.to_crs(crs_meters) if original_crs is not None else gdf.copy()
|
|
80
|
-
right = (
|
|
81
|
-
ref_gdf.to_crs(crs_meters)
|
|
82
|
-
if ref_gdf.crs is not None and str(ref_gdf.crs) != crs_meters
|
|
83
|
-
else ref_gdf.copy()
|
|
84
|
-
)
|
|
85
|
-
right = right.reset_index(drop=True)
|
|
86
|
-
|
|
87
|
-
has_id = ref_id_col is not None and ref_id_col in right.columns
|
|
88
|
-
line_geoms = list(right.geometry)
|
|
89
|
-
index = SpatialIndex(line_geoms)
|
|
90
|
-
|
|
91
|
-
n = len(left)
|
|
92
|
-
edge_ids: list[object] = [None] * n
|
|
93
|
-
measures = np.full(n, np.nan, dtype=float)
|
|
94
|
-
offsets = np.full(n, np.nan, dtype=float)
|
|
95
|
-
snapped = np.zeros(n, dtype=bool)
|
|
96
|
-
new_geoms = list(left.geometry)
|
|
97
|
-
|
|
98
|
-
for row_i, geom in enumerate(left.geometry):
|
|
99
|
-
if geom is None or geom.is_empty:
|
|
100
|
-
continue
|
|
101
|
-
pt = geom if geom.geom_type == "Point" else geom.centroid
|
|
102
|
-
|
|
103
|
-
best = self._best_line(index, line_geoms, pt, max_distance_m)
|
|
104
|
-
if best is None:
|
|
105
|
-
# Record how far the truly nearest line is (informative).
|
|
106
|
-
near = index.nearest(pt)
|
|
107
|
-
if near:
|
|
108
|
-
offsets[row_i] = float(line_geoms[near[0]].distance(pt))
|
|
109
|
-
continue
|
|
110
|
-
|
|
111
|
-
pos, dist = best
|
|
112
|
-
line = line_geoms[pos]
|
|
113
|
-
measure = line.project(pt)
|
|
114
|
-
measures[row_i] = float(measure)
|
|
115
|
-
offsets[row_i] = float(dist)
|
|
116
|
-
snapped[row_i] = True
|
|
117
|
-
edge_ids[row_i] = right.iloc[pos][ref_id_col] if has_id else pos
|
|
118
|
-
new_geoms[row_i] = line.interpolate(measure)
|
|
119
|
-
|
|
120
|
-
out = left.copy()
|
|
121
|
-
out[edge_id_col] = edge_ids
|
|
122
|
-
out[measure_col] = measures
|
|
123
|
-
out[offset_col] = offsets
|
|
124
|
-
out[snapped_col] = snapped
|
|
125
|
-
out = out.set_geometry(
|
|
126
|
-
gpd.GeoSeries(new_geoms, index=out.index, crs=crs_meters)
|
|
127
|
-
)
|
|
128
|
-
if original_crs is not None:
|
|
129
|
-
out = out.to_crs(original_crs)
|
|
130
|
-
return out.reset_index(drop=True)
|
|
131
|
-
|
|
132
|
-
@staticmethod
|
|
133
|
-
def _best_line(
|
|
134
|
-
index: SpatialIndex,
|
|
135
|
-
line_geoms: list,
|
|
136
|
-
pt,
|
|
137
|
-
max_distance_m: float | None,
|
|
138
|
-
) -> "tuple[int, float] | None":
|
|
139
|
-
"""Return (line position, distance) of the best match, or None."""
|
|
140
|
-
if max_distance_m is None:
|
|
141
|
-
near = index.nearest(pt)
|
|
142
|
-
if not near:
|
|
143
|
-
return None
|
|
144
|
-
pos = near[0]
|
|
145
|
-
return pos, float(line_geoms[pos].distance(pt))
|
|
146
|
-
|
|
147
|
-
candidates = index.query_radius(pt, max_distance_m)
|
|
148
|
-
best_pos, best_dist = -1, float("inf")
|
|
149
|
-
for pos in candidates:
|
|
150
|
-
d = line_geoms[pos].distance(pt)
|
|
151
|
-
if d < best_dist:
|
|
152
|
-
best_pos, best_dist = pos, d
|
|
153
|
-
if best_pos < 0 or best_dist > max_distance_m:
|
|
154
|
-
return None
|
|
155
|
-
return best_pos, best_dist
|
|
156
|
-
|
|
157
|
-
def get_schema(self) -> dict:
|
|
158
|
-
return {
|
|
159
|
-
"type": "object",
|
|
160
|
-
"properties": {
|
|
161
|
-
"ref_layer": {
|
|
162
|
-
"type": "string",
|
|
163
|
-
"description": "Reference line layer.",
|
|
164
|
-
},
|
|
165
|
-
"ref_id_col": {
|
|
166
|
-
"type": ["string", "null"],
|
|
167
|
-
"description": "Column on the lines providing edge_id.",
|
|
168
|
-
},
|
|
169
|
-
"max_distance_m": {
|
|
170
|
-
"type": ["number", "null"],
|
|
171
|
-
"description": "Max snap distance (m). None = nearest line always.",
|
|
172
|
-
},
|
|
173
|
-
"edge_id_col": {"type": "string", "default": "edge_id"},
|
|
174
|
-
"measure_col": {"type": "string", "default": "measure"},
|
|
175
|
-
"offset_col": {"type": "string", "default": "offset_distance"},
|
|
176
|
-
"snapped_col": {"type": "string", "default": "snapped"},
|
|
177
|
-
"crs_meters": {"type": "string", "default": "EPSG:3857"},
|
|
178
|
-
},
|
|
179
|
-
# ``ref_layer`` is pipeline plumbing (stripped before validation)
|
|
180
|
-
# so it cannot be in ``required``; the runtime raises a clear
|
|
181
|
-
# ValueError when ``ref_gdf`` is None.
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
__all__ = ["SnapPointsToLinesCapability"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/middleware/metrics_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_datasets_router.py
RENAMED
|
File without changes
|
{gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_features_router.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_upload_router.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|