@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.
- package/README.md +0 -1
- package/dist/components/axis.d.ts +1 -1
- package/dist/control/ZoomPanHandler.d.ts +1 -1
- package/dist/datautils/picks.d.ts +6 -6
- package/dist/datautils/schematicShapeGenerator.d.ts +2 -2
- package/dist/datautils/seismicimage.d.ts +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -272
- package/dist/interfaces.d.ts +1 -1
- package/dist/layers/CalloutCanvasLayer.d.ts +2 -2
- package/dist/layers/CustomDisplayObjects/ComplexRope.d.ts +1 -1
- package/dist/layers/GeomodelCanvasLayer.d.ts +1 -1
- package/dist/layers/ImageCanvasLayer.d.ts +1 -1
- package/dist/layers/ReferenceLineLayer.d.ts +29 -0
- package/dist/layers/SeismicCanvasLayer.d.ts +2 -3
- package/dist/layers/index.d.ts +1 -0
- package/dist/layers/schematicInterfaces.d.ts +4 -4
- package/dist/utils/arc-length.d.ts +1 -1
- package/dist/utils/root-finder.d.ts +1 -1
- package/package.json +38 -33
- package/src/components/axis.ts +247 -0
- package/src/components/index.ts +1 -0
- package/src/constants.ts +17 -0
- package/src/control/ExtendedCurveInterpolator.ts +155 -0
- package/src/control/IntersectionReferenceSystem.ts +391 -0
- package/src/control/LayerManager.ts +294 -0
- package/src/control/MainController.ts +296 -0
- package/src/control/ZoomPanHandler.ts +436 -0
- package/src/control/index.ts +5 -0
- package/src/control/interfaces.ts +42 -0
- package/src/control/overlay.ts +118 -0
- package/src/datautils/camelcase.ts +28 -0
- package/src/datautils/colortable.ts +14 -0
- package/src/datautils/findsample.ts +64 -0
- package/src/datautils/index.ts +6 -0
- package/src/datautils/interfaces.ts +68 -0
- package/src/datautils/picks.ts +328 -0
- package/src/datautils/schematicShapeGenerator.ts +919 -0
- package/src/datautils/seismicimage.ts +180 -0
- package/src/datautils/surfacedata.ts +318 -0
- package/src/datautils/trajectory.ts +206 -0
- package/src/index.ts +6 -0
- package/src/interfaces.ts +99 -0
- package/src/layers/CalloutCanvasLayer.ts +338 -0
- package/src/layers/CustomDisplayObjects/ComplexRope.ts +45 -0
- package/src/layers/CustomDisplayObjects/ComplexRopeGeometry.ts +190 -0
- package/src/layers/CustomDisplayObjects/FixedWidthSimpleRope.ts +41 -0
- package/src/layers/CustomDisplayObjects/FixedWidthSimpleRopeGeometry.ts +149 -0
- package/src/layers/CustomDisplayObjects/UniformTextureStretchRope.ts +39 -0
- package/src/layers/CustomDisplayObjects/UniformTextureStretchRopeGeometry.ts +174 -0
- package/src/layers/GeomodelCanvasLayer.ts +176 -0
- package/src/layers/GeomodelLabelsLayer.ts +619 -0
- package/src/layers/GeomodelLayerV2.ts +110 -0
- package/src/layers/GridLayer.ts +145 -0
- package/src/layers/ImageCanvasLayer.ts +55 -0
- package/src/layers/ReferenceLineLayer.ts +185 -0
- package/src/layers/SchematicLayer.ts +829 -0
- package/src/layers/SeismicCanvasLayer.ts +46 -0
- package/src/layers/WellborePathLayer.ts +129 -0
- package/src/layers/base/CanvasLayer.ts +102 -0
- package/src/layers/base/HTMLLayer.ts +70 -0
- package/src/layers/base/Layer.ts +217 -0
- package/src/layers/base/PixiLayer.ts +190 -0
- package/src/layers/base/SVGLayer.ts +63 -0
- package/src/layers/base/index.ts +5 -0
- package/src/layers/index.ts +16 -0
- package/src/layers/schematicInterfaces.ts +433 -0
- package/src/utils/arc-length.ts +66 -0
- package/src/utils/binary-search.ts +26 -0
- package/src/utils/color.ts +22 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/root-finder.ts +78 -0
- package/src/utils/text.ts +88 -0
- 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
|
+
};
|