Skip to main content

energyml_section/
model.rs

1//! The data of the section view - what goes in, what one package gives, what
2//! the calculation answers, and how it is looked at.
3//!
4//! ADR-0015: **this file is the contract
5//! between the modules** (`read`, `assemble`, `crosses`, `layout`, `svg`): they
6//! are written against these types, never against each other's internals.
7//!
8//! Everything here is `serde`, in camelCase, because it travels: to the front end
9//! (the catalogue and the view), into the SVG's metadata (the inputs and the
10//! view), and back from it (`energyml profile --from-svg`).
11
12use serde::{Deserialize, Serialize};
13
14/// The version of the catalogue's format. A reader meeting another one refuses
15/// by name rather than guessing what a field meant.
16pub const FORMAT: u32 = 1;
17
18// ---------------------------------------------------------------------------
19// Identity
20
21/// A horizontal coordinate system: an EPSG code and the unit its coordinates
22/// are written in - `energyml_domain::reproject::System`, serialisable.
23#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct System {
26    /// The EPSG code.
27    pub epsg: u64,
28    /// The unit, as `energyml_domain::units` spells it (`"m"`, `"ft"`, …).
29    pub unit: String,
30}
31
32/// An object of a package, as the section view names it everywhere.
33#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct ObjectId {
36    /// The package it is in - the front end's store id (`s1`, `s2`, …).
37    pub store: String,
38    /// Its uri in that package.
39    pub uri: String,
40    /// Its uuid, lower case.
41    pub uuid: String,
42    /// Its citation title, as the package writes it (possibly empty).
43    pub title: String,
44    /// Its qualified type (`resqml22.TriangulatedSetRepresentation`).
45    pub qualified_type: String,
46}
47
48impl ObjectId {
49    /// The key of an object across stores: store and uri joined by a NUL - one
50    /// spelling, so a key read off the SVG (`data-key`) is a key any consumer
51    /// of this model can build from the same two parts.
52    pub fn key(&self) -> String {
53        object_key(&self.store, &self.uri)
54    }
55}
56
57/// The key of an object, from its store and uri: the two joined by a NUL.
58pub fn object_key(store: &str, uri: &str) -> String {
59    format!("{store}\u{0}{uri}")
60}
61
62// ---------------------------------------------------------------------------
63// Inputs - what, if it changes, makes a new catalogue
64
65/// What the calculation is made of. Two catalogues built from equal inputs are
66/// the same catalogue: this is the cache key, and the SVG's metadata.
67#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct Inputs {
70    /// The leading trace, `[x, y]` in the scene's system - where it was pointed.
71    pub trace: Vec<[f64; 2]>,
72    /// That system; `None` for a scene with none (every object then has to be
73    /// in its own frame, and an object that names a system is refused).
74    pub system: Option<System>,
75    /// The long section's corridor, left and right of the trace as it was drawn.
76    pub left: f64,
77    /// See [`Inputs::left`].
78    pub right: f64,
79    /// The cross sections, if any.
80    pub crosses: Crosses,
81    /// The objects asked for, package by package.
82    pub objects: Vec<Asked>,
83    /// The scene's time scale - metres of the vertical axis per unit of time -
84    /// when an object in time is asked for. `None`: an object in
85    /// time is refused by name.
86    pub time_factor: Option<f64>,
87}
88
89/// What one package is asked for.
90#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(rename_all = "camelCase")]
92pub struct Asked {
93    /// The package.
94    pub store: String,
95    /// Its revision when asked - part of the cache key: an edit makes a new
96    /// contribution from this package, and only from it.
97    pub revision: u64,
98    /// Wellbore trajectories and frames, by uri.
99    pub wells: Vec<String>,
100    /// Anything with triangles - surfaces, and IJK grids (their envelope), by uri.
101    pub surfaces: Vec<String>,
102}
103
104/// The cross sections of a long section.
105#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
106#[serde(tag = "kind", rename_all = "camelCase")]
107pub enum Crosses {
108    /// No cross section.
109    None,
110    /// One every `spacing` along the trace's arc length.
111    #[serde(rename_all = "camelCase")]
112    Every {
113        /// The step, in the trace's unit.
114        spacing: f64,
115        /// What every cross section takes, unless it has its own.
116        common: CrossWidths,
117        /// The cross sections set apart, by index.
118        own: Vec<CrossOwn>,
119    },
120    /// `count` of them, spread evenly along the trace.
121    #[serde(rename_all = "camelCase")]
122    Count {
123        /// How many.
124        count: u32,
125        /// See [`Crosses::Every`].
126        common: CrossWidths,
127        /// See [`Crosses::Every`].
128        own: Vec<CrossOwn>,
129    },
130}
131
132/// A cross section's two pairs of widths: its **length** either side of
133/// the long section, and its **corridor** for projecting wells.
134#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
135#[serde(rename_all = "camelCase")]
136pub struct CrossWidths {
137    /// Length on the left of the long section (seen along the trace).
138    pub before: f64,
139    /// Length on the right of the long section.
140    pub after: f64,
141    /// Its corridor, left of the cross section as it runs (left to right of the trace).
142    pub left: f64,
143    /// See [`CrossWidths::left`].
144    pub right: f64,
145}
146
147/// One cross section set apart from the common widths.
148#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct CrossOwn {
151    /// Its index along the trace, from 0.
152    pub index: u32,
153    /// Its widths.
154    pub widths: CrossWidths,
155}
156
157// ---------------------------------------------------------------------------
158// Sections
159
160/// Which section: the long one, or a cross section by index.
161#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
162#[serde(tag = "kind", rename_all = "camelCase")]
163pub enum SectionKey {
164    /// The long section, along the leading trace.
165    Long,
166    /// A cross section.
167    Cross {
168        /// Its index along the trace.
169        index: u32,
170    },
171}
172
173/// A section's trace in plan, as `crosses::traces` computes it from the inputs -
174/// what `read` cuts along and `assemble` projects on.
175#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct SectionTrace {
178    /// Which section.
179    pub key: SectionKey,
180    /// Its polyline in plan, `[x, y]` of the scene. A cross section's runs from
181    /// its `before` end to its `after` end.
182    pub trace: Vec<[f64; 2]>,
183    /// Its corridor.
184    pub left: f64,
185    /// See [`SectionTrace::left`].
186    pub right: f64,
187    /// For a cross section, where it crosses the long section: the abscissa on
188    /// the long section, and on itself. `None` for the long section.
189    pub crossing: Option<[f64; 2]>,
190}
191
192// ---------------------------------------------------------------------------
193// What one package gives - `read`'s answer, before any projection
194
195/// What one package gives the calculation (`read::contribution`). Pure data in
196/// the scene's system: `assemble` needs no source to project it.
197#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct Contribution {
200    /// The package.
201    pub store: String,
202    /// Its wells, read.
203    pub wells: Vec<WellRead>,
204    /// Its surfaces and grids, cut along every section trace.
205    pub surfaces: Vec<SurfaceRead>,
206    /// What was asked and could not be read, with the reason.
207    pub refused: Vec<Refused>,
208    /// What was read but not all of it kept - a well's nodes or intervals that
209    /// are not numbers (`read::finite`). The object is drawn, so this is not a
210    /// refusal; `assemble` passes these on to the catalogue's notes.
211    #[serde(default)]
212    pub notes: Vec<Note>,
213}
214
215/// An object refused, and why - in English, naming what was refused.
216#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub struct Refused {
219    /// The object, as far as it could be named.
220    pub object: ObjectId,
221    /// Why.
222    pub reason: String,
223}
224
225/// Whether a well object is a frame or a trajectory.
226#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub enum WellKind {
229    /// A `WellboreFrameRepresentation` (carries strata).
230    Frame,
231    /// A `WellboreTrajectoryRepresentation`.
232    Trajectory,
233}
234
235/// One well, read: its head and its path in the scene's system, its strata.
236#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct WellRead {
239    /// The object.
240    pub object: ObjectId,
241    /// Frame or trajectory.
242    pub kind: WellKind,
243    /// The wellbore it describes - what makes a frame and its trajectory one
244    /// line: the uuid of the wellbore feature, or of the trajectory a
245    /// frame hangs on when no feature is reachable. `None`: it stands alone.
246    pub wellbore: Option<String>,
247    /// The head, `[x, y, z]` - the datum, never the first node.
248    pub head: [f64; 3],
249    /// The nodes drawn, `[x, y, z]`.
250    pub path: Vec<[f64; 3]>,
251    /// The MD of each node, when known (same length as `path`).
252    pub md: Option<Vec<f64>>,
253    /// The strata intervals along the MD.
254    pub intervals: Vec<Interval>,
255    /// Why there are no intervals, when there are none and some were wanted.
256    pub strata: Option<String>,
257    /// The intervals are **borrowed** from other frames of the wellbore, looked
258    /// up at this one's MDs - not stated by this object. What makes the frame
259    /// that states them keep the line (`assemble`).
260    #[serde(default)]
261    pub borrowed: bool,
262    /// Told by its head alone - **not read**: no node, no interval. A well far
263    /// from every section (`read::contribution_bounded`); `assemble` never folds
264    /// it into a wellbore's line (nothing to rank it by) and refuses it on its
265    /// own row, from its head.
266    #[serde(default)]
267    pub head_only: bool,
268}
269
270/// A stratigraphic interval along a well, in MD.
271#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
272#[serde(rename_all = "camelCase")]
273pub struct Interval {
274    /// Where it starts, MD.
275    pub from: f64,
276    /// Where it ends, MD.
277    pub to: f64,
278    /// Its colour, `#rrggbb` or `#rrggbbaa`, resolved by the package's cascade -
279    /// `None` when the package gives none (drawn in the well's colour, never
280    /// left transparent).
281    pub colour: Option<String>,
282    /// The unit's title.
283    pub unit: String,
284    /// The unit's **full uri** (uuid and version, as the package holds it) -
285    /// what lets a view say the colour of the unit after the catalogue was made
286    /// ([`View::unit_colours`]). `None` when the unit cannot be named without a
287    /// guess (two units of one title and colour in the frame, or one the package
288    /// does not hold): its baked `colour` is then all there is.
289    #[serde(default, skip_serializing_if = "Option::is_none")]
290    pub unit_uri: Option<String>,
291}
292
293/// What a surface is, as its interpretation says - never its title.
294#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
295#[serde(rename_all = "camelCase")]
296pub enum TraceRole {
297    /// A horizon.
298    Horizon,
299    /// A fault.
300    Fault,
301    /// Anything else, or nothing said.
302    Other,
303}
304
305/// One surface or grid, read and cut along every section.
306#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
307#[serde(rename_all = "camelCase")]
308pub struct SurfaceRead {
309    /// The object.
310    pub object: ObjectId,
311    /// What it is.
312    pub role: TraceRole,
313    /// Read in time and carried onto the depth axis by `time_factor`.
314    pub time: bool,
315    /// The colour the package gives it - its `GraphicalInformationSet`, by the
316    /// library's cascade (representation, interpretation, feature) - or `None`
317    /// when it gives none. A colour of the **file**, never of the scene.
318    #[serde(default)]
319    pub colour: Option<String>,
320    /// Its age, `[old, young]` in millions of years before present, when its
321    /// interpretation states one as numbers (`read::surfaces::age_of`); `None`
322    /// when the file does not - an age is never inferred.
323    #[serde(default)]
324    pub age: Option<[f64; 2]>,
325    /// The uuid of the feature its interpretation interprets (lowercase), when
326    /// the package can be followed that far. Two surfaces of the same feature
327    /// are two versions of one horizon, and a fill never goes from one to the
328    /// other. `None` when unreadable: nothing is grouped by a title or by
329    /// geometry.
330    #[serde(default)]
331    pub feature: Option<String>,
332    /// Its cut along each section; a section it does not cross has no entry.
333    pub cuts: Vec<SurfaceCut>,
334}
335
336/// A surface or grid cut along one section.
337#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
338#[serde(rename_all = "camelCase")]
339pub struct SurfaceCut {
340    /// Which section.
341    pub section: SectionKey,
342    /// What is drawn.
343    pub shape: CutShape,
344    /// What the cut could not say - a folded span dropped, and why.
345    pub notes: Vec<String>,
346    /// Where each point of a `Trace` cut comes from on the surface, parallel
347    /// to `spans` (one list per span, one source per point) - what lets a
348    /// colour given **per node** of the surface (`View::paints`) be laid along
349    /// the cut. Empty for an envelope, and when the reader cannot say: the cut
350    /// is then drawn in the object's flat colour, as before.
351    #[serde(default)]
352    pub sources: Vec<Vec<CutSource>>,
353}
354
355/// A point of a cut, as a place on an edge of the surface: `node a + t·(node b
356/// − node a)`, the nodes numbered **as the representation numbers them** - the
357/// running count of its patches' nodes, holes included, which is what a
358/// per-node colour buffer of the representation indexes (`colour_buffer`). The
359/// reader drops non-finite nodes before it cuts; `read::surfaces` translates
360/// its indices back (`Prepared::nodes`), cross-audited on a real model by a
361/// downstream test. An end clipped to the trace's window
362/// lies inside a triangle, not on an edge: it takes the nearer crossing's
363/// edge, a fraction of a triangle at the two ends of a span.
364#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
365pub struct CutSource {
366    /// The first node of the edge.
367    pub a: u32,
368    /// The second node of the edge.
369    pub b: u32,
370    /// Where on the edge, from `a` (0) to `b` (1).
371    pub t: f64,
372}
373
374/// The cut of a surface, or of a grid's envelope.
375#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
376#[serde(tag = "kind", rename_all = "camelCase")]
377pub enum CutShape {
378    /// A triangulated surface: continuous spans, `(s, z)`, s increasing
379    /// (`fence::curve`).
380    Trace {
381        /// The spans.
382        spans: Vec<Vec<[f64; 2]>>,
383    },
384    /// An IJK grid's envelope.
385    Envelope {
386        /// The top, in spans.
387        top: Vec<Vec<[f64; 2]>>,
388        /// The bottom, in spans.
389        bottom: Vec<Vec<[f64; 2]>>,
390        /// Closed polygons between them, broken where the grid has holes.
391        fills: Vec<Vec<[f64; 2]>>,
392        /// The faces of split coordinate lines, cut.
393        faults: Vec<Vec<[f64; 2]>>,
394    },
395}
396
397// ---------------------------------------------------------------------------
398// The catalogue - `assemble`'s answer
399
400/// What the calculation answers: everything that is drawn, in data.
401#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
402#[serde(rename_all = "camelCase")]
403pub struct Catalogue {
404    /// [`FORMAT`].
405    pub format: u32,
406    /// What it was made from.
407    pub inputs: Inputs,
408    /// The long section.
409    pub long: Section,
410    /// The cross sections, by increasing abscissa on the trace.
411    pub crosses: Vec<Section>,
412    /// The view from above.
413    pub plan: Plan,
414    /// Every object asked for, and what became of it.
415    pub objects: Vec<ObjectRow>,
416    /// What the calculation noticed without it being a refusal.
417    pub notes: Vec<Note>,
418}
419
420/// One section, drawn in `(s, z)`.
421#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
422#[serde(rename_all = "camelCase")]
423pub struct Section {
424    /// Which section.
425    pub key: SectionKey,
426    /// Its trace in plan, scene `[x, y]`.
427    pub trace: Vec<[f64; 2]>,
428    /// Its length.
429    pub length: f64,
430    /// Its marks along `s`: vertices, crossings.
431    pub marks: Vec<Mark>,
432    /// The band its corridor keeps, scene `[x, y]`, counter-clockwise, not closed.
433    pub band: Vec<[f64; 2]>,
434    /// What is drawn on it.
435    pub items: Vec<Item>,
436    /// The vertical range of what is drawn; `None` when nothing is.
437    pub z_range: Option<[f64; 2]>,
438}
439
440/// A mark along a section's abscissa.
441#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
442#[serde(tag = "kind", rename_all = "camelCase")]
443pub enum Mark {
444    /// A vertex of the section's trace, by its index.
445    Vertex {
446        /// Where.
447        s: f64,
448        /// Which vertex.
449        index: u32,
450    },
451    /// Where another section crosses this one.
452    Crossing {
453        /// Where.
454        s: f64,
455        /// Which section crosses here.
456        other: SectionKey,
457    },
458}
459
460/// What is drawn on a section.
461#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
462#[serde(tag = "kind", rename_all = "camelCase")]
463pub enum Item {
464    /// A well projected.
465    Well(WellItem),
466    /// A surface cut.
467    Trace(TraceItem),
468    /// A grid's envelope cut.
469    Envelope(EnvelopeItem),
470}
471
472/// Which side of a section a well's head is on, read along the section.
473#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
474#[serde(rename_all = "camelCase")]
475pub enum Side {
476    /// On the left.
477    Left,
478    /// On the right.
479    Right,
480}
481
482/// A well, projected on a section.
483#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
484#[serde(rename_all = "camelCase")]
485pub struct WellItem {
486    /// The object drawn (the frame when its wellbore has one).
487    pub object: ObjectId,
488    /// Abscissa of the head's projection.
489    pub s: f64,
490    /// Plan distance from the head to the section.
491    pub d: f64,
492    /// Which side.
493    pub side: Side,
494    /// The head, `(s, z)`.
495    pub head: [f64; 2],
496    /// Each node, `(s, z)`.
497    pub path: Vec<[f64; 2]>,
498    /// The MD of each node, when known.
499    pub md: Option<Vec<f64>>,
500    /// The strata intervals.
501    pub intervals: Vec<Interval>,
502    /// Why there are none, when there are none.
503    pub strata: Option<String>,
504    /// The other objects of the same wellbore, folded into this line.
505    pub merged: Vec<ObjectId>,
506}
507
508/// A surface, cut along a section.
509#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
510#[serde(rename_all = "camelCase")]
511pub struct TraceItem {
512    /// The object.
513    pub object: ObjectId,
514    /// What it is.
515    pub role: TraceRole,
516    /// The spans, `(s, z)`.
517    pub spans: Vec<Vec<[f64; 2]>>,
518    /// Read in time, carried by the time factor.
519    pub time: bool,
520    /// What the cut could not say.
521    pub notes: Vec<String>,
522    /// The colour the package gives it ([`SurfaceRead::colour`]).
523    #[serde(default)]
524    pub colour: Option<String>,
525    /// Its age ([`SurfaceRead::age`]); absent when the file states none.
526    #[serde(default)]
527    pub age: Option<[f64; 2]>,
528    /// The feature it is a version of ([`SurfaceRead::feature`]).
529    #[serde(default)]
530    pub feature: Option<String>,
531    /// Where each point of each span comes from on the surface
532    /// ([`SurfaceCut::sources`]), parallel to `spans` - what a paint of the
533    /// front end (`View::paints`) is laid along. Empty when the reader cannot say.
534    #[serde(default)]
535    pub sources: Vec<Vec<CutSource>>,
536}
537
538/// A grid's envelope, cut along a section.
539#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
540#[serde(rename_all = "camelCase")]
541pub struct EnvelopeItem {
542    /// The object.
543    pub object: ObjectId,
544    /// The top, in spans.
545    pub top: Vec<Vec<[f64; 2]>>,
546    /// The bottom, in spans.
547    pub bottom: Vec<Vec<[f64; 2]>>,
548    /// Filled polygons between them.
549    pub fills: Vec<Vec<[f64; 2]>>,
550    /// Fault faces cut.
551    pub faults: Vec<Vec<[f64; 2]>>,
552    /// Read in time.
553    pub time: bool,
554    /// The colour the package gives it ([`SurfaceRead::colour`]).
555    #[serde(default)]
556    pub colour: Option<String>,
557}
558
559/// The view from above, in local coordinates: the first point of the trace is
560/// `(0, 0)` - no six-figure coordinates in the SVG.
561#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
562#[serde(rename_all = "camelCase")]
563pub struct Plan {
564    /// The first point of the trace, in the scene's system: what `(0, 0)` is.
565    pub origin: [f64; 2],
566    /// That system.
567    pub system: Option<System>,
568    /// Every section's trace, local.
569    pub sections: Vec<(SectionKey, Vec<[f64; 2]>)>,
570    /// Every section's band, local.
571    pub bands: Vec<(SectionKey, Vec<[f64; 2]>)>,
572    /// Every well asked for, with where it went.
573    pub wells: Vec<PlanWell>,
574}
575
576/// A well in the plan.
577#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
578#[serde(rename_all = "camelCase")]
579pub struct PlanWell {
580    /// The object.
581    pub object: ObjectId,
582    /// Its head, local `[x, y]`.
583    pub head: [f64; 2],
584    /// Where it was projected, on which section, with its foot (local) - empty
585    /// for a well projected nowhere.
586    pub feet: Vec<(SectionKey, [f64; 2])>,
587    /// Why it was projected nowhere, when it was not (beyond an end, too far…).
588    pub rejected: Option<String>,
589}
590
591/// An object asked for, and what became of it.
592#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
593#[serde(rename_all = "camelCase")]
594pub struct ObjectRow {
595    /// The object.
596    pub object: ObjectId,
597    /// Well, surface or grid.
598    pub kind: RowKind,
599    /// What became of it.
600    pub outcome: Outcome,
601    /// Whether the front end may offer a fill up or down from this object: a
602    /// surface that is a horizon or anything but a fault - triangulated or a 2D
603    /// grid alike. Never a fault (a sub-vertical surface bounds no unit), an
604    /// IJK grid (its envelope has its own fills) or a well. Decided here, once;
605    /// the front end only reads it.
606    #[serde(default)]
607    pub fillable: bool,
608}
609
610/// What kind of object a row is.
611#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
612#[serde(rename_all = "camelCase")]
613pub enum RowKind {
614    /// A well.
615    Well,
616    /// A surface cut in a line: a triangulated surface or a 2D grid.
617    Surface,
618    /// An IJK grid.
619    Grid,
620}
621
622/// What became of an object asked for.
623#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
624#[serde(tag = "kind", rename_all = "camelCase")]
625pub enum Outcome {
626    /// Drawn on these sections.
627    Drawn {
628        /// Where.
629        sections: Vec<SectionKey>,
630    },
631    /// Folded into another line of the same wellbore.
632    Merged {
633        /// The line it went into.
634        into: ObjectId,
635    },
636    /// Read, but it crosses no section - not a refusal.
637    Missed,
638    /// Refused, and why.
639    Refused {
640        /// Why, naming what.
641        reason: String,
642    },
643}
644
645/// Something the calculation noticed: cross sections running through each
646/// other, a disagreement at a crossing…
647#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
648#[serde(rename_all = "camelCase")]
649pub struct Note {
650    /// A stable word for what it is (`crossesCross`, `crossingMismatch`, …),
651    /// which the front end turns into a sentence.
652    pub code: String,
653    /// The sections concerned.
654    pub sections: Vec<SectionKey>,
655    /// The objects concerned.
656    pub objects: Vec<ObjectId>,
657    /// The sentence, in English, for the CLI and the console.
658    pub said: String,
659}
660
661// ---------------------------------------------------------------------------
662// The view - how a catalogue is looked at
663
664/// What to draw and how - nothing here makes a new catalogue.
665#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
666#[serde(rename_all = "camelCase")]
667pub struct View {
668    /// A section, or the plan.
669    pub target: Target,
670    /// The window drawn; `None`: everything, with a margin.
671    pub window: Option<Window>,
672    /// A portion of the trace: `[s start, s end]`, a truncation - the
673    /// abscissas keep their origin at the trace's first point.
674    pub portion: Option<[f64; 2]>,
675    /// The drawing's size in pixels.
676    pub size_px: [f64; 2],
677    /// The background colour.
678    pub background: String,
679    /// Objects hidden in this drawing, by key.
680    pub hidden: Vec<String>,
681    /// Colours the scene gives objects, by key - the front end's colour stack,
682    /// resolved by the front end (never a second resolution here).
683    pub colours: Vec<(String, String)>,
684    /// The colour of each stratigraphic unit **now**, by the key of its well's
685    /// package and the unit's full uri ([`object_key`] of `store` and
686    /// [`Interval::unit_uri`]), resolved by the library's colour walks. Read
687    /// before the colour baked into an interval, by the wells **and** by the
688    /// strata legend, so that the two cannot disagree - and a colour edited
689    /// after the catalogue was made shows without making a new one. Empty: the
690    /// baked colours, as drawings always were (and then **not written** into
691    /// the metadata).
692    #[serde(default, skip_serializing_if = "Vec::is_empty")]
693    pub unit_colours: Vec<(String, String)>,
694    /// Line widths in pixels, by object key: each overrides, for that object
695    /// only, the default width (`well_width_px` for a well). A width that is
696    /// not a finite number in `(0, MAX_WIDTH_PX]` is refused by name in the
697    /// drawing's warnings and the default is drawn.
698    #[serde(default)]
699    pub widths: Vec<(String, f64)>,
700    /// How wells are coloured.
701    pub well_colouring: WellColouring,
702    /// How wide a well is drawn, in pixels.
703    pub well_width_px: f64,
704    /// For each surface, which way its colour fills.
705    pub fills: Vec<FillChoice>,
706    /// What is shown.
707    pub show: Show,
708    /// What the horizontal axis says.
709    pub axis: AxisMode,
710    /// Objects put forward on purpose - exported.
711    pub highlight: Vec<String>,
712    /// How objects are named on the drawing: their whole title, or a short code
713    /// (`H0`, `W3`, `F1`…) and a legend beside it.
714    #[serde(default)]
715    pub labels: LabelMode,
716    /// What the front end's scene paints along an object, by key: one colour per
717    /// node, resolved by the front end from what its 3D view already holds (a
718    /// property, the strata) - never a second resolution here (the resolver is supplied by the front end). An object without
719    /// one is drawn as before: that is the default resolver.
720    #[serde(default)]
721    pub paints: Vec<(String, Paint)>,
722    /// Marks along a painted well - at every node, or where its colour
723    /// changes - the 3D view's joints.
724    #[serde(default)]
725    pub joints: Joint,
726    /// Write, into the SVG, the script that lets it zoom itself when opened
727    /// alone in a browser: lines keep their width in pixels, objects part
728    /// (on by default).
729    #[serde(default = "zoom_on")]
730    pub zoom_script: bool,
731    /// A legend of the stratigraphic units the drawn wells show, each with the
732    /// colour its interval is drawn in, on the left of the drawing and in a
733    /// group of its own (`<g id="strata-legend">`, an Inkscape layer) that is
734    /// easy to move or edit. Off by default, and then **not
735    /// written** into the metadata: a drawing made before the option existed
736    /// is the same bytes.
737    #[serde(default, skip_serializing_if = "is_off")]
738    pub strata_legend: bool,
739    /// A short black tick across the well at every boundary between two
740    /// intervals of a strata-coloured well, in the well's own group.
741    /// Off by default, and then **not written** into the metadata.
742    #[serde(default, skip_serializing_if = "is_off")]
743    pub strata_ticks: bool,
744    /// Under strata colouring, draw the portions of a well that carry no strata
745    /// information (below the last interval, between two) in the well's own
746    /// colour - as drawings always did. Off: a well is drawn only where a unit
747    /// is known (its head and label stay), and a note counts the wells that lost
748    /// portions. On by default, and then **not written** into
749    /// the metadata, so earlier drawings keep their bytes.
750    ///
751    /// **A legacy alias of [`UnstratifiedStyle::Hidden`]**, kept so that every
752    /// document written before the styles still reads: `false` here is the same
753    /// as `unstratified: hidden`, and wins over it ([`View::unstratified_style`]
754    /// is the one place that decides). New code sets `unstratified`.
755    #[serde(default = "shown", skip_serializing_if = "is_on")]
756    pub draw_unstratified: bool,
757    /// How the portions of a well with no strata information are drawn, under
758    /// strata colouring (several styles are offered: fixed colour, texture, dashes…). The default is the solid neutral grey drawings always had,
759    /// and then it is **not written** into the metadata, so earlier drawings
760    /// keep their bytes. [`crate::style::declared`] lists the styles and their
761    /// parameters; the front end builds its form from that list.
762    #[serde(default, skip_serializing_if = "UnstratifiedStyle::is_default")]
763    pub unstratified: UnstratifiedStyle,
764    /// What each package is called, by the front end's store id (`s1` → the file's
765    /// name): the strata legend writes it beside a unit shown more than once -
766    /// the same unit, two colours, because two packages do not agree on it
767    /// (the legend says which package the entries sharing a name come from). A store with no name
768    /// here is named by its id. Empty, and then **not written** into the
769    /// metadata, so earlier drawings keep their bytes.
770    #[serde(default, skip_serializing_if = "Vec::is_empty")]
771    pub store_names: Vec<(String, String)>,
772    /// Named markers on a set of objects - a symbol or a contour at the head,
773    /// the bottom or a depth of each - listed in a legend of their own
774    /// (ADR-0015). Empty,
775    /// and then **not written** into the metadata, so earlier drawings keep
776    /// their bytes.
777    #[serde(default, skip_serializing_if = "Vec::is_empty")]
778    pub markers: Vec<Marker>,
779}
780
781/// A named marker put on a set of objects: what the legend of markers lists.
782/// Which shapes and places exist is **declared** by
783/// [`crate::markers::declared`]; the front end builds its form from that list.
784#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
785#[serde(rename_all = "camelCase")]
786pub struct Marker {
787    /// The title in the legend. Never empty.
788    pub name: String,
789    /// The id of a declared shape ([`crate::markers::DeclaredShape::id`]).
790    pub shape: String,
791    /// `#rrggbb` or `#rrggbbaa`.
792    pub colour: String,
793    /// Where on each object.
794    pub at: MarkerPlace,
795    /// The objects, by the front end's key ([`object_key`]).
796    pub keys: Vec<String>,
797    /// The symbol's size, pixels (the width of a contour is the object's, plus a halo).
798    pub size_px: f64,
799    /// Kept in the view and not drawn: no symbol, no legend entry, no note
800    /// (a marker can be hidden without being deleted). Off by
801    /// default, and then **not written**.
802    #[serde(default, skip_serializing_if = "is_off")]
803    pub hidden: bool,
804}
805
806/// Where a marker sits on an object. Serialised as `{ "kind": "md", "value": 1500 }`.
807#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
808#[serde(tag = "kind", rename_all = "camelCase")]
809pub enum MarkerPlace {
810    /// The head of a well, the first point of a trace.
811    Head,
812    /// The last point of the drawn path.
813    Bottom,
814    /// The point of a well at this measured depth, in the unit of the well's MDs.
815    Md {
816        /// The measured depth.
817        value: f64,
818    },
819}
820
821/// The neutral grey a well's own line is drawn in when nothing gives it a colour.
822pub const NEUTRAL_COLOUR: &str = "#555555";
823
824/// How the portions of a well that no interval covers are drawn. Serialised as
825/// `{ "kind": "dashed", "colour": "#999999", "dash": 6, "gap": 4 }`; the kinds
826/// and their parameters are declared by [`crate::style::declared`], which a test
827/// holds against this enum.
828#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
829#[serde(tag = "kind", rename_all = "camelCase")]
830pub enum UnstratifiedStyle {
831    /// Not drawn at all.
832    Hidden,
833    /// A plain line in one colour.
834    Solid {
835        /// Any SVG colour.
836        colour: String,
837    },
838    /// A dashed line.
839    Dashed {
840        /// Any SVG colour.
841        colour: String,
842        /// The length of a dash, pixels.
843        dash: f64,
844        /// The gap between two dashes, pixels.
845        gap: f64,
846    },
847    /// A texture: parallel stripes, an SVG `<pattern>` the line is stroked with.
848    Hatched {
849        /// Any SVG colour.
850        colour: String,
851        /// The distance between two stripes, pixels.
852        spacing: f64,
853        /// The stripes' angle, degrees.
854        angle: f64,
855    },
856}
857
858impl Default for UnstratifiedStyle {
859    /// Solid neutral grey: what a drawing always showed there.
860    fn default() -> Self {
861        UnstratifiedStyle::Solid {
862            colour: NEUTRAL_COLOUR.to_string(),
863        }
864    }
865}
866
867impl UnstratifiedStyle {
868    /// Whether this is the default - what is not written.
869    pub fn is_default(&self) -> bool {
870        *self == Self::default()
871    }
872
873    /// The reason a style cannot be drawn, if any: a colour that is empty, a
874    /// length that is not a finite number in its range. Said by name in the
875    /// drawing's notes, and the default is drawn.
876    pub fn refusal(&self) -> Option<String> {
877        let colour = |c: &str| (c.trim().is_empty()).then(|| "its colour is empty".to_string());
878        let range = |name: &str, v: f64, lo: f64, hi: f64| {
879            (!(v.is_finite() && v >= lo && v <= hi))
880                .then(|| format!("its {name} ({v}) is not a number in [{lo}, {hi}]"))
881        };
882        match self {
883            Self::Hidden => None,
884            Self::Solid { colour: c } => colour(c),
885            Self::Dashed { colour: c, dash, gap } => colour(c)
886                .or_else(|| range("dash", *dash, style_limits::LENGTH.0, style_limits::LENGTH.1))
887                .or_else(|| range("gap", *gap, style_limits::LENGTH.0, style_limits::LENGTH.1)),
888            Self::Hatched {
889                colour: c,
890                spacing,
891                angle,
892            } => colour(c)
893                .or_else(|| {
894                    range(
895                        "spacing",
896                        *spacing,
897                        style_limits::SPACING.0,
898                        style_limits::SPACING.1,
899                    )
900                })
901                .or_else(|| range("angle", *angle, style_limits::ANGLE.0, style_limits::ANGLE.1)),
902        }
903    }
904}
905
906/// The ranges the style parameters are held to - **the same numbers**
907/// [`crate::style::declared`] gives the front end as `min` and `max`.
908pub mod style_limits {
909    /// A dash or a gap, pixels.
910    pub const LENGTH: (f64, f64) = (1.0, 100.0);
911    /// The distance between stripes, pixels.
912    pub const SPACING: (f64, f64) = (2.0, 50.0);
913    /// A stripe's angle, degrees.
914    pub const ANGLE: (f64, f64) = (0.0, 180.0);
915}
916
917fn is_on(on: &bool) -> bool {
918    *on
919}
920
921fn is_off(on: &bool) -> bool {
922    !*on
923}
924
925fn zoom_on() -> bool {
926    true
927}
928
929/// A colour per node of an object, as the scene shows it.
930#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
931#[serde(rename_all = "camelCase")]
932pub struct Paint {
933    /// `#rrggbbaa`, one per node of the object in the order the module reads
934    /// its nodes (`colourBuffer`) - alpha 0: no value there, a gap, never a
935    /// colour.
936    pub nodes: Vec<String>,
937    /// How the colour runs from one node to the next.
938    #[serde(default)]
939    pub blend: Blend,
940}
941
942/// How a paint is read between two nodes - a front end's `Blend`.
943#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
944#[serde(rename_all = "camelCase")]
945pub enum Blend {
946    /// Node `k`'s colour over `[k, k+1)`: a discrete property, the strata.
947    #[default]
948    Net,
949    /// From node `k`'s colour to node `k+1`'s: a continuous property.
950    Smooth,
951}
952
953/// Where a well is marked - a front end's `JointKind`.
954#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
955#[serde(rename_all = "camelCase")]
956pub enum JointKind {
957    /// Nowhere.
958    #[default]
959    None,
960    /// At every node.
961    Nodes,
962    /// Where the colour changes.
963    Changes,
964}
965
966/// A mark along a painted well: a stretch of `px` pixels centred on the node,
967/// in `colour` at `alpha` - alpha 0 is a gap.
968#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
969#[serde(rename_all = "camelCase")]
970pub struct Joint {
971    /// Where.
972    pub kind: JointKind,
973    /// Its length along the well, pixels of the drawing.
974    pub px: f64,
975    /// `#rrggbb`.
976    pub colour: String,
977    /// 0 (a gap) to 1.
978    pub alpha: f64,
979}
980
981impl Default for Joint {
982    fn default() -> Self {
983        Self {
984            kind: JointKind::None,
985            px: 3.0,
986            colour: "#000000".to_string(),
987            alpha: 1.0,
988        }
989    }
990}
991
992/// The widest line a view may ask for, pixels.
993pub const MAX_WIDTH_PX: f64 = 40.0;
994
995/// How objects are named on a drawing.
996#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
997#[serde(rename_all = "camelCase")]
998pub enum LabelMode {
999    /// The title, in full (cut with "…" past a width).
1000    #[default]
1001    Full,
1002    /// A letter and a number - `W` wells, `H` horizons, `F` faults, `S` other
1003    /// surfaces, `G` grids - numbered in an order the whole catalogue shares, so
1004    /// an object has the same code on every section and on the plan; the titles
1005    /// are in a legend beside the drawing.
1006    Codes,
1007}
1008
1009impl View {
1010    /// The style the unstratified portions are drawn in: `hidden` when the
1011    /// legacy `draw_unstratified` is off, whatever `unstratified` says - the one
1012    /// place the two are read together.
1013    pub fn unstratified_style(&self) -> UnstratifiedStyle {
1014        if self.draw_unstratified {
1015            self.unstratified.clone()
1016        } else {
1017            UnstratifiedStyle::Hidden
1018        }
1019    }
1020
1021    /// A view of `target` at `size_px`, everything else at its default: the
1022    /// whole section, a white background, wells coloured by their strata,
1023    /// names, distances, vertices and crossings shown, the projected distance
1024    /// on the axis.
1025    pub fn new(target: Target, size_px: [f64; 2]) -> Self {
1026        Self {
1027            target,
1028            window: None,
1029            portion: None,
1030            size_px,
1031            background: "#ffffff".to_string(),
1032            hidden: Vec::new(),
1033            colours: Vec::new(),
1034            unit_colours: Vec::new(),
1035            widths: Vec::new(),
1036            well_colouring: WellColouring::Strata,
1037            well_width_px: 3.0,
1038            fills: Vec::new(),
1039            show: Show {
1040                crossings: true,
1041                vertices: true,
1042                distances: true,
1043                names: true,
1044                rejected: false,
1045                bands: true,
1046                surface_names: true,
1047                notes: true,
1048                origin: true,
1049                direction: true,
1050            },
1051            axis: AxisMode::Projected,
1052            highlight: Vec::new(),
1053            labels: LabelMode::Full,
1054            paints: Vec::new(),
1055            joints: Joint::default(),
1056            zoom_script: true,
1057            strata_legend: false,
1058            strata_ticks: false,
1059            draw_unstratified: true,
1060            unstratified: UnstratifiedStyle::default(),
1061            store_names: Vec::new(),
1062            markers: Vec::new(),
1063        }
1064    }
1065}
1066
1067/// What a view draws.
1068#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1069#[serde(tag = "kind", rename_all = "camelCase")]
1070pub enum Target {
1071    /// A section, in `(s, z)`.
1072    Section {
1073        /// Which.
1074        key: SectionKey,
1075    },
1076    /// The plan, local `(x, y)`.
1077    Plan,
1078}
1079
1080/// A window: `[s or x min, s or x max] × [z or y min, z or y max]`.
1081#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
1082#[serde(rename_all = "camelCase")]
1083pub struct Window {
1084    /// Horizontal range.
1085    pub x: [f64; 2],
1086    /// Vertical range.
1087    pub y: [f64; 2],
1088}
1089
1090/// How wells are coloured.
1091#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1092#[serde(rename_all = "camelCase")]
1093pub enum WellColouring {
1094    /// By side of the section.
1095    Side,
1096    /// The colour the scene gives them (`View::colours`).
1097    Scene,
1098    /// By strata intervals.
1099    Strata,
1100}
1101
1102/// Which way a surface's colour fills.
1103#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1104#[serde(rename_all = "camelCase")]
1105pub struct FillChoice {
1106    /// The surface, by key.
1107    pub key: String,
1108    /// Up to the next line above.
1109    pub up: bool,
1110    /// Down to the next line below.
1111    pub down: bool,
1112}
1113
1114/// What a drawing shows, beyond the items.
1115#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1116#[serde(rename_all = "camelCase")]
1117pub struct Show {
1118    /// Marks where other sections cross.
1119    pub crossings: bool,
1120    /// Marks the trace's vertices on the axis.
1121    pub vertices: bool,
1122    /// Each well's distance and side under its name.
1123    pub distances: bool,
1124    /// Well names.
1125    pub names: bool,
1126    /// In the plan: wells projected nowhere.
1127    pub rejected: bool,
1128    /// In the plan: the bands.
1129    pub bands: bool,
1130    /// Each surface's name, at the end of its trace.
1131    #[serde(default = "shown")]
1132    pub surface_names: bool,
1133    /// What the drawing says about itself under its plot - refused objects,
1134    /// wells set aside, labels folded. Off where another drawing beside it
1135    /// says the same (the front end's plan band, above its section).
1136    #[serde(default = "shown")]
1137    pub notes: bool,
1138    /// Where the trace's first point (`s = 0`) is, with its EPSG code, at the
1139    /// centre of the title band.
1140    #[serde(default = "shown")]
1141    pub origin: bool,
1142    /// In the plan: a chevron along each section's line, pointing the way the
1143    /// abscissa grows from the line's point 0.
1144    #[serde(default = "shown")]
1145    pub direction: bool,
1146}
1147
1148/// A setting that is on unless a document written before it says otherwise.
1149fn shown() -> bool {
1150    true
1151}
1152
1153/// What the horizontal axis of a section says.
1154#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1155#[serde(rename_all = "camelCase")]
1156pub enum AxisMode {
1157    /// Distance along the trace, in the projected system.
1158    Projected,
1159    /// Distance along the trace on the ellipsoid.
1160    Geodesic,
1161    /// The true coordinates of the points of the axis.
1162    Coordinates,
1163}