Skip to content

Feat/crs assign - #286

Open
MaxNumerique wants to merge 17 commits into
nextfrom
feat/crs_assign
Open

MaxNumerique wants to merge 17 commits into
nextfrom
feat/crs_assign

Conversation

@MaxNumerique

Copy link
Copy Markdown
Contributor

No description provided.

@MaxNumerique

Copy link
Copy Markdown
Contributor Author

CRS support — back-end part

Part of the CRS work for Geode-solutions/Vease#798. Overview of the 4 PRs and merge order: see Geode-solutions/Vease#818. This PR goes first.

New crs blueprint — routes/crs/blueprint_crs.py (/opengeodeweb_back/crs/...)

Route Params Response Notes
geographic_list geode_object_type crs_list: [{authority, code, name}] Replaces /geographic_coordinate_systems (removed, no alias: no project to keep compatible yet). 2D or 3D list depending on the type
active id crs_name, crs_info? crs_info only when the active CRS is geographic
assign_geographic id, authority, code, name crs Label only, coordinates unchanged. Only the native file is rewritten
convert_geographic id, authority, code, name crs, binary_light_viewable 400 if no geographic CRS yet, or if PROJ rejects the coordinates (out of domain)
align id, source_points[3], target_points[3], authority, code, name crs, binary_light_viewable 3-point affine transform (create_crs), then assigns the target CRS. 400 if a geographic CRS is already set, or if points are colinear

Common rules:

  • Supported objects: CRS_GEODE_OBJECTS in crs_functions.py (BRep, Section, PointSet 2D/3D, EdgedCurve 2D/3D, SurfaceMesh 2D/3D, SolidMesh3D and their subclasses). Others → 400. Unknown id → 404.
  • Only CRS from the PROJ list are accepted. geographic_coordinate_systems(is_3d) builds an AUTHORITY:CODE → info dict once (functools.cache, ~13k entries), and known_geographic_info looks it up. Unknown CRS → 400. The name stored comes from that list, not from the request. Before this, an unknown CRS was saved, and the next conversion crashed the whole process (segfault in OGRCoordinateTransformation::Transform).
  • Assigning or converting to a CRS that is already active → 400. A registered but inactive CRS with the same name is deleted first (_prepare_crs_name), so it is recomputed instead of reactivating stale coordinates.

Models and meshes — geode_objects/*

  • New methods on the CRS-capable wrappers: crs_manager(), delete_crs, assign_crs, convert_crs, create_crs. Models (BRep, Section) have no CRS manager of their own: they apply the operation to every component mesh, and crs_manager() reads the first component (ValueError if the model has none).
  • geode_objects/crs_coordinates.py → delete_crs_with_coordinates: when a geographic CRS is deleted, its coordinate vertex attribute is deleted too, unless the active CRS still uses it. Without this, each convert round trip left a full coordinate array in the native file (874 → 2604 bytes after 6 Lambert↔UTM round trips on a 1-point PointSet).

CRS in data responses

  • crs_functions.active_crs(geode_object) returns {authority, code, name} or None. It catches ValueError/RuntimeError and logs a warning: the CRS is extra information and must never fail a data response.
  • utils_functions.save_all_viewables_and_return_info adds crs when present, so save_viewable_file, the create/* routes and project import return it. It is computed from the loaded object, not stored in the database: the native file stays the source of truth.
  • Optional crs declared in the response schemas: save_viewable_file, create/point_set, create/edged_curve, create/polygonal_surface, and the CRS routes.

⚠️ Temporary PROJ workaround

In packaged apps (PyInstaller), libproj is loaded from _internal/ instead of opengeode/lib64/, and opengeode/share/proj is not bundled. PROJ then cannot find proj.db (ERROR 1: PROJ: proj_get_crs_info_list_from_database: Cannot find proj.db): the CRS list is empty and conversions fail. This is what broke the Vease browser e2e on CI (Linux and Windows).

  • crs_functions.py: os.environ.setdefault("PROJ_DATA", <opengeode>/share/proj). An existing PROJ_DATA is respected; no effect in a regular venv.
  • opengeodeweb-back.spec: collect_data_files('opengeode', subdir='share').
  • Both are marked TEMPORARY. The proper fix belongs in OpenGeode: set PROJ_DATA (or OSRSetPROJSearchPaths) on import, and ship a PyInstaller hook (pyinstaller40 entry point) collecting share/. Until then, every packaged back using CRS needs the spec line (Vease-Back is done in feat(crsAssign): crs support Vease-Back#158; PEGGHy-Back and Vease-Modeling-Back will need it).

Other changes

  • test_utils.py: check_upload_file / check_save_viewable_file moved here from tests/test_routes.py to be shared.
  • pyproject.toml: crs.schemas package data.
  • .releaserc: semantic-release success/fail comments disabled (unrelated to CRS).

Tests — tests/test_crs_routes.py

geographic_list; active; assign (and errors: 404, unsupported type, already active); convert (and errors, out of domain); align (and colinear points, already georeferenced); reassign after convert; crs returned by save_viewable_file for a georeferenced native file; unknown CRS on assign/align/convert and name taken from the list; no file growth over round trips; assign leaves viewables untouched; unreadable CRS does not fail active_crs; PROJ_DATA points to an existing proj.db.

Known limitations

  • RegularGrid 2D/3D subclass SurfaceMesh2D/SolidMesh3D, so convert/align accept them, but only the vertices move, not the grid origin/spacing. Left as is for now.
  • No lock on concurrent writes to the same data (same pattern as apply_time_series).

…ible assign

- Assign keeps the original coordinates as the "points" CRS, so every
  re-align restarts from them (Align is no longer refused on georeferenced
  data).
- Source points carry a `picked` flag: picked points are mapped from the
  displayed coordinates back to "points", typed points are original ones.
- assign_geographic returns `implausible` when the data lies outside the
  CRS area of use (read from the PROJ database shipped with opengeode).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxNumerique
MaxNumerique force-pushed the feat/crs_assign branch 2 times, most recently from 1640532 to dc7b139 Compare October 27, 2026 09:28
MaxNumerique and others added 4 commits October 27, 2026 09:28
- New `mode` param on crs/align: "similarity" (default) or "affine".
- Similarity takes 1 pair (translation) or 2 pairs (translation,
  rotation and uniform scale). Two frames with perpendicular axes of
  equal length go through the existing create_*_coordinate_system,
  then every Z of the aligned CRS is scaled around Z = 0 (meshes and
  every BRep component).
- The response returns the applied `scale` and `rotation` (degrees).
- Similarity onto a non projected CRS (degrees) is refused: Z would be
  flattened and (lat, lon) axes would mirror the data.
- Affine keeps the 3 pairs behaviour (X/Y only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New optional `linked_ids` on crs/align: data sharing the local frame
  of the aligned one (a model and its wells) are aligned in the same
  request, with the same frames and Z scale.
- The alignment is computed once, on the aligned data (picked points
  mapped back to its original coordinates); each linked data restarts
  from its own original coordinates.
- Every data is loaded and checked before any file is written: an
  unknown or unsupported linked id leaves everything untouched.
- The response lists the linked data with their CRS and light viewable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- assign_geographic and convert_geographic take optional linked_ids,
  like align: every data is loaded and changed in memory before any
  file is written, so an unknown or invalid linked data leaves
  everything untouched.
- The responses list the linked data: their CRS, plus the plausibility
  for assign and the new light viewable for convert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"code": {
"type": "string"
},
"name": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utilisé ?

source = _coordinate_system(source_points)
target = _coordinate_system(target_points)
except RuntimeError: # OpenGeode cannot build a coordinate system from colinear points
flask.abort(400, "Points must not be colinear")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Et si l'erreur est autre chose ?

AFFINE_PAIRS = 3


def _prepare_crs_name(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pourquoi ne pas mettre tout ça dans crs_functions et ne laisser que le routes dans blueprint ?

assign_geographic takes an optional only_plausible flag. The coordinates
of every data are checked before any change; with the flag, a data that
does not fit the CRS is neither assigned nor saved, and its response has
no crs. The front uses it to give the project CRS to data without CRS
automatically. Without the flag, the assignment is unchanged and the
plausibility stays a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants