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.
Files changed (336) hide show
  1. {gispulse-2.2.1/src/gispulse.egg-info → gispulse-2.2.2}/PKG-INFO +1 -1
  2. {gispulse-2.2.1 → gispulse-2.2.2}/pyproject.toml +1 -1
  3. gispulse-2.2.2/src/gispulse/capabilities/vector/snap_points.py +259 -0
  4. {gispulse-2.2.1 → gispulse-2.2.2/src/gispulse.egg-info}/PKG-INFO +1 -1
  5. gispulse-2.2.1/src/gispulse/capabilities/vector/snap_points.py +0 -185
  6. {gispulse-2.2.1 → gispulse-2.2.2}/LICENSE +0 -0
  7. {gispulse-2.2.1 → gispulse-2.2.2}/LICENSE-COMMERCIAL.md +0 -0
  8. {gispulse-2.2.1 → gispulse-2.2.2}/README.md +0 -0
  9. {gispulse-2.2.1 → gispulse-2.2.2}/setup.cfg +0 -0
  10. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/__init__.py +0 -0
  11. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/_compat.py +0 -0
  12. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/_pyogrio_warnings.py +0 -0
  13. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/__init__.py +0 -0
  14. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/apicarto.py +0 -0
  15. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/__init__.py +0 -0
  16. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/action_dispatcher.py +0 -0
  17. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/bus_message.py +0 -0
  18. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/circuit_breaker.py +0 -0
  19. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/dlq.py +0 -0
  20. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/enums.py +0 -0
  21. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/event_router.py +0 -0
  22. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/pg_notify.py +0 -0
  23. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/pool.py +0 -0
  24. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/predicate_evaluator.py +0 -0
  25. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/state_store.py +0 -0
  26. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/trigger_manager.py +0 -0
  27. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/workers/__init__.py +0 -0
  28. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/workers/base_worker.py +0 -0
  29. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/workers/dispatch_worker.py +0 -0
  30. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/esb/workers/identify_worker.py +0 -0
  31. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/__init__.py +0 -0
  32. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/app.py +0 -0
  33. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/auth.py +0 -0
  34. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/dataset_ops.py +0 -0
  35. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/dependencies.py +0 -0
  36. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/error_handlers.py +0 -0
  37. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/event_hub.py +0 -0
  38. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/layer_utils.py +0 -0
  39. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/middleware/__init__.py +0 -0
  40. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/middleware/audit_middleware.py +0 -0
  41. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/middleware/metrics_middleware.py +0 -0
  42. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/middleware/read_only.py +0 -0
  43. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/portal_app.py +0 -0
  44. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/rate_limit.py +0 -0
  45. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/__init__.py +0 -0
  46. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/_upload_utils.py +0 -0
  47. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/auth_router.py +0 -0
  48. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/capabilities_router.py +0 -0
  49. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/catalog_router.py +0 -0
  50. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/datasets_router.py +0 -0
  51. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/esb_router.py +0 -0
  52. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/examples_router.py +0 -0
  53. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/filter_router.py +0 -0
  54. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/jobs_router.py +0 -0
  55. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/marketplace_router.py +0 -0
  56. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/ogc_features_router.py +0 -0
  57. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/pipelines_router.py +0 -0
  58. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_datasets_router.py +0 -0
  59. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_features_router.py +0 -0
  60. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_router.py +0 -0
  61. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_sql_router.py +0 -0
  62. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/portal_upload_router.py +0 -0
  63. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/projects_router.py +0 -0
  64. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/relations_router.py +0 -0
  65. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/rules_router.py +0 -0
  66. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/scenarios_router.py +0 -0
  67. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/schedules_router.py +0 -0
  68. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/sessions_router.py +0 -0
  69. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/system_router.py +0 -0
  70. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/templates_router.py +0 -0
  71. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/tiles_router.py +0 -0
  72. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/triggers_router.py +0 -0
  73. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/viewer_router.py +0 -0
  74. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/watchers_router.py +0 -0
  75. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/routers/ws_router.py +0 -0
  76. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/schemas.py +0 -0
  77. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/http/serve_app.py +0 -0
  78. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/mcp/__init__.py +0 -0
  79. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/mcp/dryrun.py +0 -0
  80. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/mcp/server.py +0 -0
  81. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/mcp/workdir.py +0 -0
  82. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/metrics.py +0 -0
  83. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/ogc/__init__.py +0 -0
  84. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/ogc/auth.py +0 -0
  85. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/ogc/loader.py +0 -0
  86. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/ogc/wfs_client.py +0 -0
  87. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/ogc/wfs_fetcher.py +0 -0
  88. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/rest/__init__.py +0 -0
  89. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/rest/rest_fetcher.py +0 -0
  90. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/rest/rest_table_fetcher.py +0 -0
  91. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/stac/__init__.py +0 -0
  92. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/stac/stac_fetcher.py +0 -0
  93. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/webhooks/__init__.py +0 -0
  94. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/adapters/webhooks/http_client.py +0 -0
  95. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/app.py +0 -0
  96. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/__init__.py +0 -0
  97. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/_attribute_sql.py +0 -0
  98. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/_geometry_sql.py +0 -0
  99. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/base.py +0 -0
  100. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/classification.py +0 -0
  101. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/clustering.py +0 -0
  102. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/density.py +0 -0
  103. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/network.py +0 -0
  104. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/network_components.py +0 -0
  105. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/network_graph.py +0 -0
  106. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/network_topology.py +0 -0
  107. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/overlay.py +0 -0
  108. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/palettes.py +0 -0
  109. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/pointcloud.py +0 -0
  110. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/polygon_topology.py +0 -0
  111. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/postgis_sql.py +0 -0
  112. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/raster.py +0 -0
  113. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/registry.py +0 -0
  114. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/relation_detector.py +0 -0
  115. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/schema.py +0 -0
  116. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/selection.py +0 -0
  117. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/spatial_stats.py +0 -0
  118. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/sql_pushdown.py +0 -0
  119. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/strategy.py +0 -0
  120. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/temporal.py +0 -0
  121. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/transforms.py +0 -0
  122. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/validation.py +0 -0
  123. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/__init__.py +0 -0
  124. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/aggregate.py +0 -0
  125. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/assign_projection.py +0 -0
  126. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/boundary.py +0 -0
  127. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/buffer.py +0 -0
  128. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/calculate.py +0 -0
  129. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/centroid_area.py +0 -0
  130. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/chaikin.py +0 -0
  131. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/classify.py +0 -0
  132. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/clip.py +0 -0
  133. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/concave_hull.py +0 -0
  134. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/diff.py +0 -0
  135. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/dissolve.py +0 -0
  136. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/extract_holes.py +0 -0
  137. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/extract_ops.py +0 -0
  138. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/filter.py +0 -0
  139. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/force_geometry_type.py +0 -0
  140. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/intersects.py +0 -0
  141. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/line_merge.py +0 -0
  142. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/line_ops.py +0 -0
  143. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/merge.py +0 -0
  144. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/nearest.py +0 -0
  145. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/offset_curve.py +0 -0
  146. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/parts.py +0 -0
  147. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/polygonize.py +0 -0
  148. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/reproject.py +0 -0
  149. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/shape_ops_advanced.py +0 -0
  150. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/shape_ops_basic.py +0 -0
  151. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/simplify.py +0 -0
  152. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/snap_grid.py +0 -0
  153. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/spatial_join.py +0 -0
  154. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/split_lines.py +0 -0
  155. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/union.py +0 -0
  156. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/capabilities/vector/voronoi.py +0 -0
  157. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/__init__.py +0 -0
  158. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/data/basemaps.json +0 -0
  159. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/data/epsg_common.json +0 -0
  160. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/models.py +0 -0
  161. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/__init__.py +0 -0
  162. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/base.py +0 -0
  163. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/basemaps.py +0 -0
  164. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/flux_ign.py +0 -0
  165. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/flux_osm.py +0 -0
  166. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/opendata_datagouv.py +0 -0
  167. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/opendata_hub.py +0 -0
  168. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/opendata_ign.py +0 -0
  169. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/projections.py +0 -0
  170. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/providers/stac_client.py +0 -0
  171. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/registry.py +0 -0
  172. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/catalog/source_bridge.py +0 -0
  173. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli.py +0 -0
  174. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_mcp.py +0 -0
  175. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_portal.py +0 -0
  176. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_track.py +0 -0
  177. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_triggers.py +0 -0
  178. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_triggers_watch.py +0 -0
  179. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/cli_watch.py +0 -0
  180. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/config.py +0 -0
  181. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/__init__.py +0 -0
  182. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/assertions.py +0 -0
  183. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/bulk_ingest.py +0 -0
  184. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/bulk_runner.py +0 -0
  185. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/cache.py +0 -0
  186. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/capability_params.py +0 -0
  187. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/conditions.py +0 -0
  188. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/config.py +0 -0
  189. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/crs.py +0 -0
  190. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/dag.py +0 -0
  191. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/data/worldwide_catalog.yml +0 -0
  192. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/data_pack_signature.py +0 -0
  193. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/dispatcher.py +0 -0
  194. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/enums.py +0 -0
  195. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/explain.py +0 -0
  196. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/__init__.py +0 -0
  197. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/base.py +0 -0
  198. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/geoparquet_s3.py +0 -0
  199. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/http_file.py +0 -0
  200. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/ogc_client.py +0 -0
  201. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/ogc_features.py +0 -0
  202. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/stac.py +0 -0
  203. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/fetchers/table_file.py +0 -0
  204. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/__init__.py +0 -0
  205. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/cache.py +0 -0
  206. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/chain.py +0 -0
  207. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/expression.py +0 -0
  208. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/expression_converter.py +0 -0
  209. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/result.py +0 -0
  210. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/service.py +0 -0
  211. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/filter/types.py +0 -0
  212. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/graph.py +0 -0
  213. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/io/__init__.py +0 -0
  214. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/io/geoparquet.py +0 -0
  215. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/licence_format.py +0 -0
  216. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/logging.py +0 -0
  217. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/manifest_v3.py +0 -0
  218. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/models.py +0 -0
  219. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/network_graph_handle.py +0 -0
  220. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/observability.py +0 -0
  221. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/pipeline.py +0 -0
  222. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/pipeline_schema.py +0 -0
  223. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/plugin_contracts.py +0 -0
  224. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/plugin_hub.py +0 -0
  225. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/plugin_model.py +0 -0
  226. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/predicates.py +0 -0
  227. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/pricing_catalog.yml +0 -0
  228. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/registry.py +0 -0
  229. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/regulatory_zoning_entry.py +0 -0
  230. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/relations.py +0 -0
  231. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/session.py +0 -0
  232. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/sources.py +0 -0
  233. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/spatial_index.py +0 -0
  234. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/sql_safety.py +0 -0
  235. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/ssrf.py +0 -0
  236. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/core/zoning_normalizer.py +0 -0
  237. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/diagnostics/__init__.py +0 -0
  238. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/diagnostics/system.py +0 -0
  239. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/dsl/__init__.py +0 -0
  240. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/dsl/expression_parser.py +0 -0
  241. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/dsl/geom_fcts.py +0 -0
  242. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/__init__.py +0 -0
  243. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/capability_executor.py +0 -0
  244. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/graph_executor.py +0 -0
  245. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/job_queue.py +0 -0
  246. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/job_queue_factory.py +0 -0
  247. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/metering.py +0 -0
  248. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/pipeline_executor.py +0 -0
  249. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/runner.py +0 -0
  250. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/scenario_runner.py +0 -0
  251. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/scheduler.py +0 -0
  252. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/session_manager.py +0 -0
  253. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/trigger_bridge.py +0 -0
  254. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/orchestration/worker.py +0 -0
  255. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/__init__.py +0 -0
  256. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/audit.py +0 -0
  257. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/auth_models.py +0 -0
  258. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/auth_repository.py +0 -0
  259. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/bridge.py +0 -0
  260. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/change_log_watcher.py +0 -0
  261. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/changelog_doctor.py +0 -0
  262. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/changelog_reader.py +0 -0
  263. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/datamart.py +0 -0
  264. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/duckdb_change_detector.py +0 -0
  265. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/duckdb_diff_engine.py +0 -0
  266. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/duckdb_engine.py +0 -0
  267. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/duckdb_engine_adapter.py +0 -0
  268. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/duckdb_relations.py +0 -0
  269. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/engine.py +0 -0
  270. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/engine_factory.py +0 -0
  271. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/file_blob_cdc.py +0 -0
  272. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/geonode.py +0 -0
  273. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg.py +0 -0
  274. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg_connection.py +0 -0
  275. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg_engine.py +0 -0
  276. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg_repository.py +0 -0
  277. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg_schema.py +0 -0
  278. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/gpkg_spatial.py +0 -0
  279. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/io.py +0 -0
  280. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/licence.py +0 -0
  281. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/loader.py +0 -0
  282. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/postgis.py +0 -0
  283. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/project_io.py +0 -0
  284. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/raster_io.py +0 -0
  285. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/repository.py +0 -0
  286. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/schedule_repository.py +0 -0
  287. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/schema.py +0 -0
  288. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/session_provisioner.py +0 -0
  289. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/sld_converter.py +0 -0
  290. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/source_watcher.py +0 -0
  291. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/spatial_queries.py +0 -0
  292. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/spatialite_engine.py +0 -0
  293. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/spatialite_session.py +0 -0
  294. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/sql_dialect.py +0 -0
  295. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/sql_guardrails.py +0 -0
  296. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/sqlite_repository.py +0 -0
  297. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/storage.py +0 -0
  298. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/style_converter.py +0 -0
  299. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/style_sidecar.py +0 -0
  300. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/tier.py +0 -0
  301. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/virtual_dataset.py +0 -0
  302. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/persistence/watcher_registry.py +0 -0
  303. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/__init__.py +0 -0
  304. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/api.py +0 -0
  305. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/datagouv_refresh.py +0 -0
  306. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/mcp_pilot.py +0 -0
  307. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/pipeline.py +0 -0
  308. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/sources.py +0 -0
  309. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/spatial.py +0 -0
  310. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/plugins/worldwide_source.py +0 -0
  311. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/__init__.py +0 -0
  312. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/engine.py +0 -0
  313. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/loader.py +0 -0
  314. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/operation_executor.py +0 -0
  315. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/predicates.py +0 -0
  316. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/trigger_evaluator.py +0 -0
  317. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/rules/validation.py +0 -0
  318. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/__init__.py +0 -0
  319. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/config_loader.py +0 -0
  320. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/dialect_scanner.py +0 -0
  321. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/duckdb_engine.py +0 -0
  322. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/engine_inference.py +0 -0
  323. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/headless_runtime.py +0 -0
  324. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/layer_registry.py +0 -0
  325. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/manifest_runner.py +0 -0
  326. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/predicate_dsl.py +0 -0
  327. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/source_watch.py +0 -0
  328. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/sqlite_retry.py +0 -0
  329. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/runtime/validation_runner.py +0 -0
  330. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse/telemetry.py +0 -0
  331. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse.egg-info/SOURCES.txt +0 -0
  332. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse.egg-info/dependency_links.txt +0 -0
  333. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse.egg-info/entry_points.txt +0 -0
  334. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse.egg-info/requires.txt +0 -0
  335. {gispulse-2.2.1 → gispulse-2.2.2}/src/gispulse.egg-info/top_level.txt +0 -0
  336. {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.1
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.1"
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.1
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