@equinor/esv-intersection 3.0.0-beta.4 → 3.0.0-beta.5

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 (75) hide show
  1. package/README.md +0 -1
  2. package/dist/components/axis.d.ts +1 -1
  3. package/dist/control/ZoomPanHandler.d.ts +1 -1
  4. package/dist/datautils/picks.d.ts +6 -6
  5. package/dist/datautils/schematicShapeGenerator.d.ts +2 -2
  6. package/dist/datautils/seismicimage.d.ts +1 -1
  7. package/dist/index.esm.js +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/index.umd.js +1 -272
  10. package/dist/interfaces.d.ts +1 -1
  11. package/dist/layers/CalloutCanvasLayer.d.ts +2 -2
  12. package/dist/layers/CustomDisplayObjects/ComplexRope.d.ts +1 -1
  13. package/dist/layers/GeomodelCanvasLayer.d.ts +1 -1
  14. package/dist/layers/ImageCanvasLayer.d.ts +1 -1
  15. package/dist/layers/ReferenceLineLayer.d.ts +29 -0
  16. package/dist/layers/SeismicCanvasLayer.d.ts +2 -3
  17. package/dist/layers/index.d.ts +1 -0
  18. package/dist/layers/schematicInterfaces.d.ts +4 -4
  19. package/dist/utils/arc-length.d.ts +1 -1
  20. package/dist/utils/root-finder.d.ts +1 -1
  21. package/package.json +38 -33
  22. package/src/components/axis.ts +247 -0
  23. package/src/components/index.ts +1 -0
  24. package/src/constants.ts +17 -0
  25. package/src/control/ExtendedCurveInterpolator.ts +155 -0
  26. package/src/control/IntersectionReferenceSystem.ts +391 -0
  27. package/src/control/LayerManager.ts +294 -0
  28. package/src/control/MainController.ts +296 -0
  29. package/src/control/ZoomPanHandler.ts +436 -0
  30. package/src/control/index.ts +5 -0
  31. package/src/control/interfaces.ts +42 -0
  32. package/src/control/overlay.ts +118 -0
  33. package/src/datautils/camelcase.ts +28 -0
  34. package/src/datautils/colortable.ts +14 -0
  35. package/src/datautils/findsample.ts +64 -0
  36. package/src/datautils/index.ts +6 -0
  37. package/src/datautils/interfaces.ts +68 -0
  38. package/src/datautils/picks.ts +328 -0
  39. package/src/datautils/schematicShapeGenerator.ts +919 -0
  40. package/src/datautils/seismicimage.ts +180 -0
  41. package/src/datautils/surfacedata.ts +318 -0
  42. package/src/datautils/trajectory.ts +206 -0
  43. package/src/index.ts +6 -0
  44. package/src/interfaces.ts +99 -0
  45. package/src/layers/CalloutCanvasLayer.ts +338 -0
  46. package/src/layers/CustomDisplayObjects/ComplexRope.ts +45 -0
  47. package/src/layers/CustomDisplayObjects/ComplexRopeGeometry.ts +190 -0
  48. package/src/layers/CustomDisplayObjects/FixedWidthSimpleRope.ts +41 -0
  49. package/src/layers/CustomDisplayObjects/FixedWidthSimpleRopeGeometry.ts +149 -0
  50. package/src/layers/CustomDisplayObjects/UniformTextureStretchRope.ts +39 -0
  51. package/src/layers/CustomDisplayObjects/UniformTextureStretchRopeGeometry.ts +174 -0
  52. package/src/layers/GeomodelCanvasLayer.ts +176 -0
  53. package/src/layers/GeomodelLabelsLayer.ts +619 -0
  54. package/src/layers/GeomodelLayerV2.ts +110 -0
  55. package/src/layers/GridLayer.ts +145 -0
  56. package/src/layers/ImageCanvasLayer.ts +55 -0
  57. package/src/layers/ReferenceLineLayer.ts +185 -0
  58. package/src/layers/SchematicLayer.ts +829 -0
  59. package/src/layers/SeismicCanvasLayer.ts +46 -0
  60. package/src/layers/WellborePathLayer.ts +129 -0
  61. package/src/layers/base/CanvasLayer.ts +102 -0
  62. package/src/layers/base/HTMLLayer.ts +70 -0
  63. package/src/layers/base/Layer.ts +217 -0
  64. package/src/layers/base/PixiLayer.ts +190 -0
  65. package/src/layers/base/SVGLayer.ts +63 -0
  66. package/src/layers/base/index.ts +5 -0
  67. package/src/layers/index.ts +16 -0
  68. package/src/layers/schematicInterfaces.ts +433 -0
  69. package/src/utils/arc-length.ts +66 -0
  70. package/src/utils/binary-search.ts +26 -0
  71. package/src/utils/color.ts +22 -0
  72. package/src/utils/index.ts +1 -0
  73. package/src/utils/root-finder.ts +78 -0
  74. package/src/utils/text.ts +88 -0
  75. package/src/utils/vectorUtils.ts +67 -0
@@ -0,0 +1,919 @@
1
+ import { IPoint, Point, Texture, WRAP_MODES } from 'pixi.js';
2
+ import { DEFAULT_TEXTURE_SIZE } from '../constants';
3
+ import {
4
+ Casing,
5
+ CasingWindow,
6
+ Cement,
7
+ CementOptions,
8
+ CementPlug,
9
+ CementPlugOptions,
10
+ CementSqueeze,
11
+ CementSqueezeOptions,
12
+ Completion,
13
+ HoleOptions,
14
+ HoleSize,
15
+ ScreenOptions,
16
+ TubingOptions,
17
+ Perforation,
18
+ PerforationOptions,
19
+ foldPerforationSubKind,
20
+ hasGravelPack,
21
+ intersect,
22
+ isSubKindCasedHoleFracPack,
23
+ getPerforationsThatStartAtHoleDiameter,
24
+ isOpenHoleFracPack,
25
+ } from '../layers/schematicInterfaces';
26
+ import { ComplexRopeSegment } from '../layers/CustomDisplayObjects/ComplexRope';
27
+ import { createNormals, offsetPoints } from '../utils/vectorUtils';
28
+
29
+ export type PerforationShape = ComplexRopeSegment;
30
+
31
+ export interface TubularRenderingObject {
32
+ leftPath: Point[];
33
+ rightPath: Point[];
34
+ }
35
+
36
+ export interface CasingRenderObject {
37
+ id: string;
38
+ kind: 'casing';
39
+ referenceDiameter: number;
40
+ referenceRadius: number;
41
+ casingWallWidth: number;
42
+ hasShoe: boolean;
43
+ bottom: number;
44
+ zIndex?: number;
45
+ sections: {
46
+ kind: 'casing' | 'casing-window';
47
+ leftPath: Point[];
48
+ rightPath: Point[];
49
+ pathPoints: Point[];
50
+ polygon: IPoint[];
51
+ }[];
52
+ }
53
+
54
+ export const getEndLines = (
55
+ rightPath: IPoint[],
56
+ leftPath: IPoint[],
57
+ ): {
58
+ top: IPoint[];
59
+ bottom: IPoint[];
60
+ } => {
61
+ return {
62
+ top: [rightPath[0], leftPath[0]],
63
+ bottom: [rightPath[rightPath.length - 1], leftPath[leftPath.length - 1]],
64
+ };
65
+ };
66
+
67
+ export const makeTubularPolygon = (rightPath: Point[], leftPath: Point[]): Point[] => {
68
+ return [...leftPath, ...rightPath.map<Point>((d) => d.clone()).reverse()];
69
+ };
70
+
71
+ export const overlaps = (top1: number, bottom1: number, top2: number, bottom2: number): boolean => top1 <= bottom2 && top2 <= bottom1;
72
+
73
+ export const strictlyOverlaps = (top1: number, bottom1: number, top2: number, bottom2: number): boolean => top1 < bottom2 && top2 < bottom1;
74
+
75
+ export const uniq = <T>(arr: T[]): T[] => Array.from<T>(new Set(arr));
76
+
77
+ const findIntersectingItems = (
78
+ start: number,
79
+ end: number,
80
+ otherStrings: (Casing | Completion)[],
81
+ holes: HoleSize[],
82
+ ): { overlappingHoles: HoleSize[]; overlappingOuterStrings: (Casing | Completion)[] } => {
83
+ const overlappingHoles = holes.filter((hole: HoleSize) => overlaps(start, end, hole.start, hole.end));
84
+
85
+ const overlappingOuterStrings = otherStrings.filter((casing: Casing | Completion) => overlaps(start, end, casing.start, casing.end));
86
+
87
+ return {
88
+ overlappingHoles,
89
+ overlappingOuterStrings,
90
+ };
91
+ };
92
+
93
+ export const getUniqueDiameterChangeDepths = (
94
+ [intervalStart, intervalEnd]: [number, number],
95
+ diameterIntervals: { start: number; end: number }[],
96
+ ): number[] => {
97
+ const epsilon = 0.0001;
98
+ const diameterChangeDepths = diameterIntervals.flatMap(
99
+ (
100
+ d, // to find diameter right before/after object
101
+ ) => [d.start - epsilon, d.start, d.end, d.end + epsilon],
102
+ );
103
+ const trimmedChangedDepths = diameterChangeDepths.filter((d) => d >= intervalStart && d <= intervalEnd); // trim
104
+
105
+ trimmedChangedDepths.push(intervalStart);
106
+ trimmedChangedDepths.push(intervalEnd);
107
+
108
+ const uniqDepths = uniq(trimmedChangedDepths);
109
+ return uniqDepths.sort((a: number, b: number) => a - b);
110
+ };
111
+
112
+ const getInnerStringDiameter = (stringType: Casing | Completion): number =>
113
+ stringType.kind === 'casing' ? stringType.innerDiameter : stringType.diameter;
114
+
115
+ export const findCementOuterDiameterAtDepth = (
116
+ attachedStrings: (Casing | Completion)[],
117
+ nonAttachedStrings: (Casing | Completion)[],
118
+ holes: HoleSize[],
119
+ depth: number,
120
+ ): number => {
121
+ const defaultCementWidth = 100; // Default to flow cement outside to show error in data
122
+
123
+ const attachedStringAtDepth = attachedStrings.find(
124
+ (casingOrCompletion: Casing | Completion) => casingOrCompletion.start <= depth && casingOrCompletion.end >= depth,
125
+ );
126
+ const attachedOuterDiameter = attachedStringAtDepth ? attachedStringAtDepth.diameter : 0;
127
+
128
+ const outerCasingAtDepth = nonAttachedStrings
129
+ .filter((casingOrCompletion: Casing | Completion) => getInnerStringDiameter(casingOrCompletion) > attachedOuterDiameter)
130
+ .sort((a: Casing | Completion, b: Casing | Completion) => getInnerStringDiameter(a) - getInnerStringDiameter(b)) // ascending
131
+ .find((casing) => casing.start <= depth && casing.end >= depth);
132
+
133
+ const holeAtDepth = holes.find((hole: HoleSize) => hole.start <= depth && hole.end >= depth && hole.diameter > attachedOuterDiameter);
134
+
135
+ if (outerCasingAtDepth) {
136
+ return getInnerStringDiameter(outerCasingAtDepth);
137
+ }
138
+
139
+ if (holeAtDepth) {
140
+ return holeAtDepth.diameter;
141
+ }
142
+
143
+ return defaultCementWidth;
144
+ };
145
+
146
+ export const findCementPlugInnerDiameterAtDepth = (
147
+ attachedStrings: (Casing | Completion)[],
148
+ nonAttachedStrings: (Casing | Completion)[],
149
+ holes: HoleSize[],
150
+ depth: number,
151
+ ): number => {
152
+ // Default to flow cement outside to show error in data
153
+ const defaultCementWidth = 100;
154
+ const attachedStringAtDepth = attachedStrings
155
+ .sort((a: Casing | Completion, b: Casing | Completion) => getInnerStringDiameter(a) - getInnerStringDiameter(b)) // ascending
156
+ .find((casingOrCompletion) => casingOrCompletion.start <= depth && casingOrCompletion.end >= depth);
157
+
158
+ if (attachedStringAtDepth) {
159
+ return getInnerStringDiameter(attachedStringAtDepth);
160
+ }
161
+
162
+ // Start from an attached diameter
163
+ const minimumDiameter = attachedStrings.length ? Math.min(...attachedStrings.map((c) => getInnerStringDiameter(c))) : 0;
164
+ const nonAttachedStringAtDepth = nonAttachedStrings
165
+ .sort((a: Casing | Completion, b: Casing | Completion) => getInnerStringDiameter(a) - getInnerStringDiameter(b)) // ascending
166
+ .find(
167
+ (casingOrCompletion: Casing | Completion) =>
168
+ casingOrCompletion.start <= depth && casingOrCompletion.end >= depth && minimumDiameter <= getInnerStringDiameter(casingOrCompletion),
169
+ );
170
+
171
+ if (nonAttachedStringAtDepth) {
172
+ return getInnerStringDiameter(nonAttachedStringAtDepth);
173
+ }
174
+
175
+ const holeAtDepth = holes.find((hole) => hole.start <= depth && hole.end >= depth && hole.diameter);
176
+
177
+ if (holeAtDepth) {
178
+ return holeAtDepth.diameter;
179
+ }
180
+
181
+ return defaultCementWidth;
182
+ };
183
+
184
+ export const createComplexRopeSegmentsForCement = (
185
+ cement: Cement,
186
+ casings: Casing[],
187
+ completion: Completion[],
188
+ holes: HoleSize[],
189
+ exaggerationFactor: number,
190
+ getPoints: (start: number, end: number) => Point[],
191
+ ): ComplexRopeSegment[] => {
192
+ const { attachedStrings, nonAttachedStrings } = splitByReferencedStrings(cement.referenceIds, casings, completion);
193
+
194
+ if (attachedStrings.length === 0) {
195
+ throw new Error(`Invalid cement data, can't find referenced casing/completion string for cement with id '${cement.id}'`);
196
+ }
197
+
198
+ attachedStrings.sort((a: Casing, b: Casing) => a.end - b.end); // ascending
199
+ const bottomOfCement = attachedStrings[attachedStrings.length - 1].end;
200
+
201
+ const { overlappingOuterStrings, overlappingHoles } = findIntersectingItems(cement.toc, bottomOfCement, nonAttachedStrings, holes);
202
+
203
+ const outerDiameterIntervals = [...overlappingOuterStrings, ...overlappingHoles].map((d) => ({
204
+ start: d.start,
205
+ end: d.end,
206
+ }));
207
+
208
+ const changeDepths = getUniqueDiameterChangeDepths([cement.toc, bottomOfCement], outerDiameterIntervals);
209
+
210
+ const diameterIntervals = changeDepths.flatMap((depth: number, index: number, list: number[]) => {
211
+ if (index === list.length - 1) {
212
+ return [];
213
+ }
214
+
215
+ const nextDepth = list[index + 1];
216
+ const diameterAtChangeDepth = findCementOuterDiameterAtDepth(attachedStrings, overlappingOuterStrings, overlappingHoles, depth);
217
+
218
+ return [{ top: depth, bottom: nextDepth, diameter: diameterAtChangeDepth * exaggerationFactor }];
219
+ });
220
+
221
+ const ropeSegments = diameterIntervals.map((interval) => ({
222
+ diameter: interval.diameter,
223
+ points: getPoints(interval.top, interval.bottom),
224
+ }));
225
+
226
+ return ropeSegments;
227
+ };
228
+
229
+ const splitByReferencedStrings = (
230
+ referenceIds: string[],
231
+ casings: Casing[],
232
+ completion: Completion[],
233
+ ): { attachedStrings: (Casing | Completion)[]; nonAttachedStrings: (Casing | Completion)[] } =>
234
+ [...casings, ...completion].reduce(
235
+ (acc, current) => {
236
+ if (referenceIds.includes(current.id)) {
237
+ return { ...acc, attachedStrings: [...acc.attachedStrings, current] };
238
+ }
239
+ return { ...acc, nonAttachedStrings: [...acc.nonAttachedStrings, current] };
240
+ },
241
+ { attachedStrings: [], nonAttachedStrings: [] },
242
+ );
243
+
244
+ export const perforationDiameterChangeDepths = (
245
+ perforation: Perforation,
246
+ diameterIntervals: {
247
+ start: number;
248
+ end: number;
249
+ }[],
250
+ ): number[] => {
251
+ const { start: topOfPerforation, end: bottomOfPerforation } = perforation;
252
+
253
+ const diameterChangeDepths = diameterIntervals.flatMap((d) => [d.start, d.end]);
254
+ const trimmedChangedDepths = diameterChangeDepths.filter((d) => d >= topOfPerforation && d <= bottomOfPerforation); // trim
255
+
256
+ trimmedChangedDepths.push(topOfPerforation);
257
+ trimmedChangedDepths.push(bottomOfPerforation);
258
+
259
+ const uniqDepths = uniq(trimmedChangedDepths);
260
+
261
+ return uniqDepths.sort((a: number, b: number) => a - b);
262
+ };
263
+
264
+ export const createComplexRopeSegmentsForCementSqueeze = (
265
+ squeeze: CementSqueeze,
266
+ casings: Casing[],
267
+ completion: Completion[],
268
+ holes: HoleSize[],
269
+ exaggerationFactor: number,
270
+ getPoints: (start: number, end: number) => Point[],
271
+ ): ComplexRopeSegment[] => {
272
+ const { attachedStrings, nonAttachedStrings } = splitByReferencedStrings(squeeze.referenceIds, casings, completion);
273
+
274
+ if (attachedStrings.length === 0) {
275
+ throw new Error(`Invalid cement squeeze data, can't find referenced casing/completion for squeeze with id '${squeeze.id}'`);
276
+ }
277
+
278
+ const { overlappingOuterStrings, overlappingHoles } = findIntersectingItems(squeeze.start, squeeze.end, nonAttachedStrings, holes);
279
+
280
+ const outerDiameterIntervals = [...overlappingOuterStrings, ...overlappingHoles].map((d) => ({
281
+ start: d.start,
282
+ end: d.end,
283
+ }));
284
+
285
+ const changeDepths = getUniqueDiameterChangeDepths([squeeze.start, squeeze.end], outerDiameterIntervals);
286
+
287
+ const diameterIntervals = changeDepths.flatMap((depth, index, list) => {
288
+ if (index === list.length - 1) {
289
+ return [];
290
+ }
291
+
292
+ const nextDepth = list[index + 1];
293
+
294
+ const diameterAtDepth = findCementOuterDiameterAtDepth(attachedStrings, overlappingOuterStrings, overlappingHoles, depth);
295
+
296
+ return [{ top: depth, bottom: nextDepth, diameter: diameterAtDepth * exaggerationFactor }];
297
+ });
298
+
299
+ const ropeSegments = diameterIntervals.map((interval) => ({
300
+ diameter: interval.diameter,
301
+ points: getPoints(interval.top, interval.bottom),
302
+ }));
303
+
304
+ return ropeSegments;
305
+ };
306
+
307
+ export const createComplexRopeSegmentsForCementPlug = (
308
+ plug: CementPlug,
309
+ casings: Casing[],
310
+ completion: Completion[],
311
+ holes: HoleSize[],
312
+ exaggerationFactor: number,
313
+ getPoints: (start: number, end: number) => Point[],
314
+ ): ComplexRopeSegment[] => {
315
+ const { attachedStrings, nonAttachedStrings } = splitByReferencedStrings(plug.referenceIds, casings, completion);
316
+
317
+ const { overlappingHoles, overlappingOuterStrings } = findIntersectingItems(plug.start, plug.end, nonAttachedStrings, holes);
318
+ const innerDiameterIntervals = [...attachedStrings, ...overlappingHoles, ...overlappingOuterStrings].map((d) => ({
319
+ start: d.start,
320
+ end: d.end,
321
+ }));
322
+
323
+ const changeDepths = getUniqueDiameterChangeDepths([plug.start, plug.end], innerDiameterIntervals);
324
+
325
+ const diameterIntervals = changeDepths.flatMap((depth, index, list) => {
326
+ if (index === list.length - 1) {
327
+ return [];
328
+ }
329
+
330
+ const nextDepth = list[index + 1];
331
+ const diameterAtDepth = findCementPlugInnerDiameterAtDepth(attachedStrings, overlappingOuterStrings, overlappingHoles, depth);
332
+
333
+ return [{ top: depth, bottom: nextDepth, diameter: diameterAtDepth * exaggerationFactor }];
334
+ });
335
+
336
+ const ropeSegments = diameterIntervals.map((interval) => ({
337
+ diameter: interval.diameter,
338
+ points: getPoints(interval.top, interval.bottom),
339
+ }));
340
+
341
+ return ropeSegments;
342
+ };
343
+
344
+ const createGradientFill = (
345
+ canvas: HTMLCanvasElement,
346
+ canvasCtx: CanvasRenderingContext2D,
347
+ firstColor: string,
348
+ secondColor: string,
349
+ startPctOffset: number,
350
+ ): CanvasGradient => {
351
+ const halfWayPct = 0.5;
352
+ const gradient = canvasCtx.createLinearGradient(0, 0, 0, canvas.height);
353
+ gradient.addColorStop(0, firstColor);
354
+ gradient.addColorStop(halfWayPct - startPctOffset, secondColor);
355
+ gradient.addColorStop(halfWayPct + startPctOffset, secondColor);
356
+ gradient.addColorStop(1, firstColor);
357
+
358
+ return gradient;
359
+ };
360
+
361
+ export const createHoleBaseTexture = ({ firstColor, secondColor }: HoleOptions, width: number, height: number): Texture => {
362
+ const canvas = document.createElement('canvas');
363
+ canvas.width = width;
364
+ canvas.height = height;
365
+ const canvasCtx = canvas.getContext('2d');
366
+
367
+ canvasCtx.fillStyle = createGradientFill(canvas, canvasCtx, firstColor, secondColor, 0);
368
+ canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
369
+
370
+ return Texture.from(canvas);
371
+ };
372
+
373
+ export const createScreenTexture = ({ scalingFactor }: ScreenOptions): Texture => {
374
+ const canvas = document.createElement('canvas');
375
+ const size = DEFAULT_TEXTURE_SIZE * scalingFactor;
376
+ canvas.width = size;
377
+ canvas.height = size;
378
+ const canvasCtx = canvas.getContext('2d');
379
+
380
+ canvasCtx.fillStyle = 'white';
381
+ canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
382
+
383
+ const baseLineWidth = size / 10; // eslint-disable-line no-magic-numbers
384
+ canvasCtx.strokeStyle = '#AAAAAA';
385
+ canvasCtx.lineWidth = baseLineWidth;
386
+ canvasCtx.beginPath();
387
+
388
+ const distanceBetweenLines = size / 3;
389
+ for (let i = -canvas.width; i < canvas.width; i++) {
390
+ canvasCtx.moveTo(-canvas.width + distanceBetweenLines * i, -canvas.height);
391
+ canvasCtx.lineTo(canvas.width + distanceBetweenLines * i, canvas.height * 2);
392
+ }
393
+ canvasCtx.stroke();
394
+ return Texture.from(canvas);
395
+ };
396
+
397
+ export const createTubingTexture = ({ innerColor, outerColor, scalingFactor }: TubingOptions): Texture => {
398
+ const size = DEFAULT_TEXTURE_SIZE * scalingFactor;
399
+
400
+ const canvas = document.createElement('canvas');
401
+ canvas.width = size;
402
+ canvas.height = size;
403
+ const canvasCtx = canvas.getContext('2d');
404
+ const gradient = canvasCtx.createLinearGradient(0, 0, 0, size);
405
+
406
+ const innerColorStart = 0.3;
407
+ const innerColorEnd = 0.7;
408
+ gradient.addColorStop(0, outerColor);
409
+ gradient.addColorStop(innerColorStart, innerColor);
410
+ gradient.addColorStop(innerColorEnd, innerColor);
411
+ gradient.addColorStop(1, outerColor);
412
+
413
+ canvasCtx.fillStyle = gradient;
414
+ canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
415
+
416
+ return Texture.from(canvas);
417
+ };
418
+
419
+ export const createCementTexture = ({ firstColor, secondColor, scalingFactor }: CementOptions): Texture => {
420
+ const canvas = document.createElement('canvas');
421
+
422
+ const size = DEFAULT_TEXTURE_SIZE * scalingFactor;
423
+ const lineWidth = scalingFactor;
424
+ canvas.width = size;
425
+ canvas.height = size;
426
+ const canvasCtx = canvas.getContext('2d');
427
+
428
+ canvasCtx.fillStyle = firstColor;
429
+ canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
430
+ canvasCtx.lineWidth = lineWidth;
431
+ canvasCtx.fillStyle = secondColor;
432
+ canvasCtx.beginPath();
433
+
434
+ const distanceBetweenLines = size / 12; // eslint-disable-line no-magic-numbers
435
+ for (let i = -canvas.width; i < canvas.width; i++) {
436
+ canvasCtx.moveTo(-canvas.width + distanceBetweenLines * i, -canvas.height);
437
+ canvasCtx.lineTo(canvas.width + distanceBetweenLines * i, canvas.height);
438
+ }
439
+ canvasCtx.stroke();
440
+
441
+ return Texture.from(canvas);
442
+ };
443
+
444
+ export const createCementPlugTexture = ({ firstColor, secondColor, scalingFactor }: CementPlugOptions): Texture => {
445
+ const canvas = document.createElement('canvas');
446
+
447
+ const size = DEFAULT_TEXTURE_SIZE * scalingFactor;
448
+ canvas.width = size;
449
+ canvas.height = size;
450
+ const canvasCtx = canvas.getContext('2d');
451
+
452
+ canvasCtx.fillStyle = firstColor;
453
+ canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
454
+ canvasCtx.lineWidth = scalingFactor;
455
+ canvasCtx.strokeStyle = secondColor;
456
+ canvasCtx.beginPath();
457
+
458
+ canvasCtx.setLineDash([20, 10]); // eslint-disable-line no-magic-numbers
459
+ const distanceBetweenLines = size / 12; // eslint-disable-line no-magic-numbers
460
+ for (let i = -canvas.width; i < canvas.width; i++) {
461
+ canvasCtx.moveTo(-canvas.width + distanceBetweenLines * i, -canvas.height);
462
+ canvasCtx.lineTo(canvas.width + distanceBetweenLines * i, canvas.height * 2);
463
+ }
464
+ canvasCtx.stroke();
465
+
466
+ return Texture.from(canvas);
467
+ };
468
+
469
+ export const createCementSqueezeTexture = ({ firstColor, secondColor, scalingFactor }: CementSqueezeOptions): Texture => {
470
+ const canvas = document.createElement('canvas');
471
+
472
+ const size = DEFAULT_TEXTURE_SIZE * scalingFactor;
473
+ const lineWidth = scalingFactor;
474
+ canvas.width = size;
475
+ canvas.height = size;
476
+
477
+ const canvasCtx = canvas.getContext('2d');
478
+ canvasCtx.lineWidth = lineWidth;
479
+ canvasCtx.fillStyle = firstColor;
480
+ canvasCtx.strokeStyle = secondColor;
481
+
482
+ canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
483
+ canvasCtx.beginPath();
484
+
485
+ canvasCtx.setLineDash([20, 10]); // eslint-disable-line no-magic-numbers
486
+ const distanceBetweenLines = size / 12; // eslint-disable-line no-magic-numbers
487
+ for (let i = -canvas.width; i < canvas.width; i++) {
488
+ canvasCtx.moveTo(-canvas.width + distanceBetweenLines * i, -canvas.height);
489
+ canvasCtx.lineTo(canvas.width + distanceBetweenLines * i, canvas.height * 2);
490
+ }
491
+ canvasCtx.stroke();
492
+
493
+ return Texture.from(canvas);
494
+ };
495
+
496
+ export const createTubularRenderingObject = (radius: number, pathPoints: IPoint[]): TubularRenderingObject => {
497
+ const normals = createNormals(pathPoints);
498
+ const rightPath = offsetPoints(pathPoints, normals, radius);
499
+ const leftPath = offsetPoints(pathPoints, normals, -radius);
500
+
501
+ return { leftPath, rightPath };
502
+ };
503
+
504
+ export type CasingInterval = {
505
+ kind: 'casing' | 'casing-window';
506
+ start: number;
507
+ end: number;
508
+ };
509
+
510
+ const createCasingInterval = (start: number, end: number): CasingInterval => ({ kind: 'casing', start, end });
511
+ const createCasingWindowInterval = (start: number, end: number): CasingInterval => ({ kind: 'casing-window', start, end });
512
+
513
+ export const getCasingIntervalsWithWindows = (casing: Casing): CasingInterval[] => {
514
+ const result = (casing.windows || [])
515
+ .filter((cw: CasingWindow) => strictlyOverlaps(casing.start, casing.end, cw.start, cw.end))
516
+ .reduce<{ intervals: CasingInterval[]; lastBottom: number }>(
517
+ ({ intervals, lastBottom }, currentWindow: CasingWindow, index: number, list: CasingWindow[]) => {
518
+ const startCasingInterval: CasingInterval | null =
519
+ // last bottom before current start?
520
+ lastBottom < currentWindow.start ? createCasingInterval(lastBottom, currentWindow.start) : null;
521
+
522
+ const updatedLastBottom = startCasingInterval ? startCasingInterval.end : lastBottom;
523
+
524
+ const windowStart = Math.max(updatedLastBottom, currentWindow.start);
525
+ const windowEnd = Math.min(casing.end, currentWindow.end);
526
+ const windowInterval: CasingInterval = createCasingWindowInterval(windowStart, windowEnd);
527
+
528
+ const nextLastBottom = windowEnd;
529
+
530
+ const isLastWindow = index === list.length - 1;
531
+ const endCasingInterval: CasingInterval | null =
532
+ isLastWindow &&
533
+ // still room for a casing interval?
534
+ nextLastBottom < casing.end
535
+ ? createCasingInterval(nextLastBottom, casing.end)
536
+ : null;
537
+
538
+ const newIntervals: CasingInterval[] = [startCasingInterval, windowInterval, endCasingInterval].filter((i) => i);
539
+
540
+ return { intervals: [...intervals, ...newIntervals], lastBottom: nextLastBottom };
541
+ },
542
+ { intervals: [], lastBottom: casing.start },
543
+ );
544
+
545
+ if (!result.intervals.length) {
546
+ return [createCasingInterval(casing.start, casing.end)];
547
+ }
548
+
549
+ return result.intervals;
550
+ };
551
+
552
+ export const prepareCasingRenderObject = (
553
+ exaggerationFactor: number,
554
+ casing: Casing,
555
+ getPathPoints: (start: number, end: number) => Point[],
556
+ ): CasingRenderObject => {
557
+ const exaggeratedDiameter = casing.diameter * exaggerationFactor;
558
+ const exaggeratedRadius = exaggeratedDiameter / 2;
559
+ const exaggeratedInnerDiameter = casing.innerDiameter * exaggerationFactor;
560
+ const exaggeratedInnerRadius = exaggeratedInnerDiameter / 2;
561
+ const casingWallWidth = exaggeratedRadius - exaggeratedInnerRadius;
562
+
563
+ const sections = getCasingIntervalsWithWindows(casing).map((casingInterval: CasingInterval) => {
564
+ const pathPoints = getPathPoints(casingInterval.start, casingInterval.end);
565
+ const { leftPath, rightPath } = createTubularRenderingObject(exaggeratedRadius, pathPoints);
566
+ return { kind: casingInterval.kind, leftPath, rightPath, pathPoints, polygon: makeTubularPolygon(leftPath, rightPath) };
567
+ });
568
+
569
+ return {
570
+ kind: 'casing',
571
+ id: casing.id,
572
+ referenceDiameter: exaggeratedDiameter,
573
+ referenceRadius: exaggeratedRadius,
574
+ sections,
575
+ casingWallWidth,
576
+ hasShoe: casing.hasShoe,
577
+ bottom: casing.end,
578
+ };
579
+ };
580
+
581
+ export const createComplexRopeSegmentsForPerforation = (
582
+ perforation: Perforation,
583
+ casings: Casing[],
584
+ holes: HoleSize[],
585
+ exaggerationFactor: number,
586
+ getPoints: (start: number, end: number) => Point[],
587
+ ): ComplexRopeSegment[] => {
588
+ const attachedCasings = perforation.referenceIds.map((referenceId: string) => casings.find((casing) => casing.id === referenceId));
589
+ if (attachedCasings.length === 0 || attachedCasings.includes(undefined)) {
590
+ throw new Error('Invalid perforation data, perforation is missing attached casing');
591
+ }
592
+
593
+ const { overlappingOuterStrings, overlappingHoles } = findIntersectingItems(perforation.start, perforation.end, casings, holes);
594
+
595
+ const outerDiameterIntervals = [...overlappingOuterStrings, ...overlappingHoles].map((d) => ({
596
+ start: d.start,
597
+ end: d.end,
598
+ }));
599
+
600
+ const changeDepths = perforationDiameterChangeDepths(perforation, outerDiameterIntervals);
601
+
602
+ const diameterIntervals = changeDepths.flatMap((depth, index, list) => {
603
+ if (index === 0) {
604
+ return [];
605
+ }
606
+
607
+ const nextDepth = list[index - 1];
608
+
609
+ const diameter = findCementOuterDiameterAtDepth(attachedCasings, overlappingOuterStrings, overlappingHoles, depth) * exaggerationFactor;
610
+
611
+ return [{ top: nextDepth, bottom: depth, diameter }];
612
+ });
613
+
614
+ const ropeSegments = diameterIntervals.map((interval) => {
615
+ const points = getPoints(interval.top, interval.bottom);
616
+
617
+ const diameter =
618
+ getPerforationsThatStartAtHoleDiameter([perforation]).length === 1 || isOpenHoleFracPack(perforation)
619
+ ? interval.diameter * 4
620
+ : interval.diameter;
621
+
622
+ return {
623
+ diameter,
624
+ points,
625
+ };
626
+ });
627
+
628
+ return ropeSegments;
629
+ };
630
+
631
+ const createFracLines = (
632
+ canvas: HTMLCanvasElement,
633
+ ctx: CanvasRenderingContext2D,
634
+ widestPerfShapeDiameter: number,
635
+ perforationOptions: PerforationOptions,
636
+ startAt: 'diameter' | 'spike',
637
+ ) => {
638
+ const { spikeWidth, fracLineHalfWidth, fracLineLength } = perforationOptions;
639
+
640
+ const amountOfSpikes = canvas.width / spikeWidth;
641
+
642
+ for (let i = 0; i < amountOfSpikes; i++) {
643
+ const right: [number, number] = [i * spikeWidth + spikeWidth, canvas.height / 2];
644
+ const top: [number, number] =
645
+ startAt === 'diameter' ? [right[0] - spikeWidth / 2, fracLineLength + widestPerfShapeDiameter] : [right[0] - spikeWidth / 2, fracLineLength];
646
+
647
+ ctx.beginPath();
648
+
649
+ const start: [number, number] = [...top];
650
+ const controlPoint1: [number, number] = [top[0] - fracLineHalfWidth, fracLineLength / 2 + (startAt === 'diameter' ? widestPerfShapeDiameter : 0)];
651
+ const middle: [number, number] = [top[0], fracLineLength / 2 + (startAt === 'diameter' ? widestPerfShapeDiameter : 0)];
652
+ const controlPoint2: [number, number] = [top[0] + fracLineHalfWidth, fracLineLength / 4 + (startAt === 'diameter' ? widestPerfShapeDiameter : 0)];
653
+ const end: [number, number] = [top[0], startAt === 'diameter' ? widestPerfShapeDiameter : 0];
654
+
655
+ ctx.bezierCurveTo(...start, ...controlPoint1, ...middle);
656
+ ctx.bezierCurveTo(...middle, ...controlPoint2, ...end);
657
+ ctx.stroke();
658
+ }
659
+
660
+ for (let i = 0; i < amountOfSpikes; i++) {
661
+ const right: [number, number] = [i * spikeWidth + spikeWidth, canvas.height / 2];
662
+ const bottom: [number, number] = [
663
+ right[0] - spikeWidth / 2,
664
+ canvas.height - fracLineLength - (startAt === 'diameter' ? widestPerfShapeDiameter : 0),
665
+ ];
666
+
667
+ ctx.beginPath();
668
+
669
+ const start: [number, number] = [...bottom];
670
+ const controlPoint1: [number, number] = [
671
+ bottom[0] - fracLineHalfWidth,
672
+ canvas.height - fracLineLength / 2 - (startAt === 'diameter' ? widestPerfShapeDiameter : 0),
673
+ ];
674
+ const middle: [number, number] = [bottom[0], canvas.height - fracLineLength / 2 - (startAt === 'diameter' ? widestPerfShapeDiameter : 0)];
675
+ const controlPoint2: [number, number] = [
676
+ bottom[0] + fracLineHalfWidth,
677
+ canvas.height - fracLineLength / 4 - (startAt === 'diameter' ? widestPerfShapeDiameter : 0),
678
+ ];
679
+ const end: [number, number] = [bottom[0], canvas.height - (startAt === 'diameter' ? widestPerfShapeDiameter : 0)];
680
+
681
+ ctx.bezierCurveTo(...start, ...controlPoint1, ...middle);
682
+ ctx.bezierCurveTo(...middle, ...controlPoint2, ...end);
683
+ ctx.stroke();
684
+ }
685
+ };
686
+
687
+ /**
688
+ * If a perforation does not overlap with another perforations of type with gravel,
689
+ * the perforation spikes are either red when open or grey when closed.
690
+ * Open and closed refers to two fields on a perforation item referencing runs.
691
+ * If a perforation overlaps with another perforation of type with gravel and the perforation is open,
692
+ * the perforation spikes should be yellow.
693
+ * If closed the perforation remains grey.
694
+ * Cased hole frac pack
695
+ * Makes perforations of type "Perforation" yellow if overlapping and perforation are open.
696
+ * Makes perforations of type "Perforation" yellow if overlapping and perforation are open.
697
+ * If no perforation of type "perforation" are overlapping, there are no fracturation lines and no spikes.
698
+ * If a perforation of type "perforation" is overlapping,
699
+ * the fracturation lines extends from the tip of the perforation spikes into formation.
700
+ * @param perforation
701
+ * @param otherPerforations
702
+ * @param widestPerfShapeDiameter
703
+ * @param perforationOptions
704
+ * @returns
705
+ */
706
+ const createSubkindPerforationTexture = (
707
+ perforation: Perforation,
708
+ otherPerforations: Perforation[],
709
+ widestPerfShapeDiameter: number,
710
+ perforationOptions: PerforationOptions,
711
+ ) => {
712
+ const canvas = document.createElement('canvas');
713
+
714
+ const size = DEFAULT_TEXTURE_SIZE * perforationOptions.scalingFactor;
715
+
716
+ canvas.width = size / 2;
717
+ canvas.height = size;
718
+ const canvasCtx = canvas.getContext('2d');
719
+
720
+ canvasCtx.fillStyle = perforationOptions.red;
721
+
722
+ const { spikeWidth, fracLineLength } = perforationOptions;
723
+
724
+ const amountOfSpikes = canvas.width / spikeWidth;
725
+
726
+ const intersectionsWithGravel: boolean = otherPerforations.some((perf) => hasGravelPack(perf) && intersect(perforation, perf));
727
+
728
+ const intersectionsWithCasedHoleFracPack: boolean = otherPerforations.some(
729
+ (perf) => isSubKindCasedHoleFracPack(perf) && intersect(perforation, perf),
730
+ );
731
+
732
+ let hasFracLines = false;
733
+
734
+ if (intersectionsWithGravel || intersectionsWithCasedHoleFracPack) {
735
+ hasFracLines = true;
736
+ if (perforation.isOpen) {
737
+ canvasCtx.fillStyle = perforationOptions.yellow;
738
+ canvasCtx.strokeStyle = perforationOptions.yellow;
739
+ } else {
740
+ canvasCtx.fillStyle = perforationOptions.grey;
741
+ canvasCtx.strokeStyle = perforationOptions.grey;
742
+ }
743
+ } else {
744
+ if (perforation.isOpen) {
745
+ canvasCtx.fillStyle = perforationOptions.red;
746
+ canvasCtx.strokeStyle = perforationOptions.red;
747
+ } else {
748
+ canvasCtx.fillStyle = perforationOptions.grey;
749
+ canvasCtx.strokeStyle = perforationOptions.grey;
750
+ }
751
+ }
752
+
753
+ for (let i = 0; i < amountOfSpikes; i++) {
754
+ const left: [number, number] = [i * spikeWidth, canvas.height / 2];
755
+ const right: [number, number] = [i * spikeWidth + spikeWidth, canvas.height / 2];
756
+ const top: [number, number] = [right[0] - spikeWidth / 2, fracLineLength];
757
+
758
+ canvasCtx.beginPath();
759
+ canvasCtx.moveTo(...top);
760
+ canvasCtx.lineTo(...left);
761
+ canvasCtx.lineTo(...right);
762
+ canvasCtx.closePath();
763
+ canvasCtx.fill();
764
+ }
765
+
766
+ for (let i = 0; i < amountOfSpikes; i++) {
767
+ const left: [number, number] = [i * spikeWidth, canvas.height / 2];
768
+ const right: [number, number] = [i * spikeWidth + spikeWidth, canvas.height / 2];
769
+ const bottom: [number, number] = [right[0] - spikeWidth / 2, canvas.height - fracLineLength];
770
+
771
+ canvasCtx.beginPath();
772
+ canvasCtx.moveTo(...left);
773
+ canvasCtx.lineTo(...bottom);
774
+ canvasCtx.lineTo(...right);
775
+ canvasCtx.closePath();
776
+ canvasCtx.fill();
777
+ }
778
+
779
+ if (hasFracLines) {
780
+ createFracLines(canvas, canvasCtx, widestPerfShapeDiameter, perforationOptions, 'spike');
781
+ }
782
+
783
+ return Texture.from(canvas, { wrapMode: WRAP_MODES.CLAMP });
784
+ };
785
+
786
+ /**
787
+ * Yellow gravel
788
+ * @param perforationOptions
789
+ * @returns
790
+ */
791
+ const createSubkindOpenHoleGravelPackTexture = (perforationOptions: PerforationOptions) => {
792
+ const canvas = document.createElement('canvas');
793
+
794
+ const size = DEFAULT_TEXTURE_SIZE * perforationOptions.scalingFactor;
795
+ const canvasCtx = canvas.getContext('2d');
796
+
797
+ canvasCtx.save();
798
+ canvasCtx.globalAlpha = perforationOptions.packingOpacity;
799
+ canvas.width = size / 2;
800
+ canvas.height = size;
801
+ canvasCtx.fillStyle = perforationOptions.yellow;
802
+ canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
803
+ canvasCtx.restore();
804
+ return Texture.from(canvas, { wrapMode: WRAP_MODES.CLAMP });
805
+ };
806
+
807
+ /**
808
+ * Yellow gravel. Yellow frac lines from hole OD into formation
809
+ * @param widestPerfShapeDiameter
810
+ * @param perforationOptions
811
+ * @returns
812
+ */
813
+ const createSubkindOpenHoleFracPackTexture = (widestPerfShapeDiameter: number, perforationOptions: PerforationOptions) => {
814
+ const canvas = document.createElement('canvas');
815
+
816
+ const size = DEFAULT_TEXTURE_SIZE * perforationOptions.scalingFactor;
817
+ canvas.width = size / 2;
818
+ canvas.height = size;
819
+ const canvasCtx = canvas.getContext('2d');
820
+
821
+ canvasCtx.fillStyle = perforationOptions.yellow;
822
+ canvasCtx.strokeStyle = perforationOptions.yellow;
823
+
824
+ const { fracLineLength, packingOpacity } = perforationOptions;
825
+
826
+ const xy: [number, number] = [0, fracLineLength + widestPerfShapeDiameter];
827
+ const wh: [number, number] = [canvas.width, widestPerfShapeDiameter];
828
+ canvasCtx.save();
829
+ canvasCtx.globalAlpha = packingOpacity;
830
+ canvasCtx.fillRect(...xy, ...wh);
831
+ canvasCtx.restore();
832
+
833
+ createFracLines(canvas, canvasCtx, widestPerfShapeDiameter, perforationOptions, 'diameter');
834
+ return Texture.from(canvas, { wrapMode: WRAP_MODES.CLAMP });
835
+ };
836
+
837
+ /**
838
+ * Cased hole fracturation
839
+ * Yellow fracturation lines from casing OD into formation
840
+ * @param widestPerfShapeDiameter
841
+ * @param perforationOptions
842
+ * @returns
843
+ */
844
+ const createSubkindCasedHoleFracturationTexture = (widestPerfShapeDiameter: number, perforationOptions: PerforationOptions) => {
845
+ const canvas = document.createElement('canvas');
846
+ const size = DEFAULT_TEXTURE_SIZE * perforationOptions.scalingFactor;
847
+ canvas.width = size / 2;
848
+ canvas.height = size;
849
+ const canvasCtx = canvas.getContext('2d');
850
+ canvasCtx.fillStyle = perforationOptions.yellow;
851
+ canvasCtx.strokeStyle = perforationOptions.yellow;
852
+ createFracLines(canvas, canvasCtx, widestPerfShapeDiameter, perforationOptions, 'diameter');
853
+
854
+ return Texture.from(canvas, { wrapMode: WRAP_MODES.CLAMP });
855
+ };
856
+
857
+ /**
858
+ * Yellow gravel. Makes perforations of type "Perforation" yellow if overlapping and perforation are open.
859
+ * @param perforationOptions
860
+ * @returns
861
+ */
862
+ const createSubkindCasedHoleGravelPackTexture = (perforationOptions: PerforationOptions) => {
863
+ const canvas = document.createElement('canvas');
864
+ const size = DEFAULT_TEXTURE_SIZE * perforationOptions.scalingFactor;
865
+ canvas.width = size / 2;
866
+ canvas.height = size;
867
+
868
+ const canvasCtx = canvas.getContext('2d');
869
+
870
+ canvasCtx.fillStyle = perforationOptions.yellow;
871
+ canvasCtx.save();
872
+ canvasCtx.globalAlpha = perforationOptions.packingOpacity;
873
+ canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
874
+ canvasCtx.restore();
875
+
876
+ return Texture.from(canvas, { wrapMode: WRAP_MODES.CLAMP });
877
+ };
878
+
879
+ /**
880
+ *
881
+ * Yellow gravel and fracturation lines.
882
+ * @param perforationOptions
883
+ * @returns
884
+ */
885
+ const createSubkindCasedHoleFracPack = (perforationOptions: PerforationOptions) => {
886
+ const canvas = document.createElement('canvas');
887
+
888
+ const size = DEFAULT_TEXTURE_SIZE * perforationOptions.scalingFactor;
889
+
890
+ canvas.width = size / 2;
891
+ canvas.height = size;
892
+ const canvasCtx = canvas.getContext('2d');
893
+
894
+ canvasCtx.fillStyle = perforationOptions.yellow;
895
+ canvasCtx.save();
896
+ canvasCtx.globalAlpha = perforationOptions.packingOpacity;
897
+ canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
898
+ canvasCtx.restore();
899
+ return Texture.from(canvas, { wrapMode: WRAP_MODES.CLAMP });
900
+ };
901
+
902
+ export const createPerforationTexture = (
903
+ perforation: Perforation,
904
+ widestPerfShapeDiameter: number,
905
+ otherPerforations: Perforation[],
906
+ perforationOptions: PerforationOptions,
907
+ ): Texture => {
908
+ return foldPerforationSubKind(
909
+ {
910
+ Perforation: () => createSubkindPerforationTexture(perforation, otherPerforations, widestPerfShapeDiameter, perforationOptions),
911
+ OpenHoleGravelPack: () => createSubkindOpenHoleGravelPackTexture(perforationOptions),
912
+ OpenHoleFracPack: () => createSubkindOpenHoleFracPackTexture(widestPerfShapeDiameter, perforationOptions),
913
+ CasedHoleFracturation: () => createSubkindCasedHoleFracturationTexture(widestPerfShapeDiameter, perforationOptions),
914
+ CasedHoleGravelPack: () => createSubkindCasedHoleGravelPackTexture(perforationOptions),
915
+ CasedHoleFracPack: () => createSubkindCasedHoleFracPack(perforationOptions),
916
+ },
917
+ perforation.subKind,
918
+ );
919
+ };