This tutorial demonstrates how to display schematics components in 3D using the Geotoolkit3D module.
# Data Initialization
First step is to set-up the data that will be used by the Schematics Node.
This is done by creating a new Trajectory3d, which defines everything the Schematics Node will need to display the schematics.
Next, create a WellBoreData which defines the components to be displayed using the Trajectory data.
import { Vector3 } from "/node_modules/.vite/deps/three.js?v=2235af65";
import { Trajectory3d } from "@int/geotoolkit3d/geodata/Trajectory3d.ts";
import { TrajectoryValue } from "@int/geotoolkit3d/geodata/TrajectoryValue.ts";
import { WellBoreData } from "@int/geotoolkit/schematics/data/WellBoreData.ts";
import { SchematicsNode } from "@int/geotoolkit3d/scene/schematics/SchematicsNode.ts";
import { Grid } from "@int/geotoolkit3d/scene/grid/Grid.ts";
import { Plot } from "@int/geotoolkit3d/Plot.ts";
import { getWell3410A44 } from "/src/code/Carnac3D/AdditionalFunctionality/Schematics/ts/data.ts";
function createScene(divElement) {
const wellData = getWell3410A44();
const plot = new Plot({
"container": divElement,
"scale": new Vector3(1, 1, 2.2)
});
const data = new Trajectory3d(
wellData.xDeviation.slice(),
wellData.yDeviation.slice(),
wellData.elevation.slice(),
new Vector3(wellData.xRef, wellData.yRef, 0),
wellData.mds.slice(),
[new TrajectoryValue("data.dls", wellData.dls.slice())]
);
const components = new WellBoreData();
components.addComponent("path", { geometry: null });
const grid = new Grid({
start: new Vector3(454589, 6780898, -2500),
end: new Vector3(459324, 6784161, 0),
modelstart: new Vector3(454589, 6780898, 2500),
modelend: new Vector3(459324, 6784161, 0)
});
const trajectory = new SchematicsNode({ "data": data, "components": components });
plot.getRoot().add(grid);
plot.getRoot().add(trajectory);
plot.fitCamera(null, false, 0);
return plot;
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# Configuring the Schematic
The schematic can be configured by adding variables to the path object.
This example shows how to color the path by the value attached to the Trajectory data called 'data.dls,' and add a couple of casing components and annotations.
import { Vector3 } from "/node_modules/.vite/deps/three.js?v=2235af65";
import { Trajectory3d } from "@int/geotoolkit3d/geodata/Trajectory3d.ts";
import { TrajectoryValue } from "@int/geotoolkit3d/geodata/TrajectoryValue.ts";
import { WellBoreData } from "@int/geotoolkit/schematics/data/WellBoreData.ts";
import { SchematicsNode } from "@int/geotoolkit3d/scene/schematics/SchematicsNode.ts";
import { Grid } from "@int/geotoolkit3d/scene/grid/Grid.ts";
import { AnnotationBase, Mode as AnnotationBaseMode } from "@int/geotoolkit3d/scene/AnnotationBase.ts";
import { DefaultColorProvider } from "@int/geotoolkit/util/DefaultColorProvider.ts";
import { Plot } from "@int/geotoolkit3d/Plot.ts";
import { DefaultAnnotationLayout } from "@int/geotoolkit3d/schematics/layout/DefaultAnnotationLayout.ts";
import { ExpandedAnnotationLayout } from "@int/geotoolkit3d/schematics/layout/ExpandedAnnotationLayout.ts";
import { getWell3410A44 } from "/src/code/Carnac3D/AdditionalFunctionality/Schematics/ts/data.ts";
const ANGLED_CAMERA_DIRECTION = new Vector3(0.5, -0.9, 0.1);
function createAnnotation(title) {
return {
"title": title,
"titlestyle": {
"font": "12px Arial",
"color": "yellow"
},
"linestyle": "yellow",
"mode": AnnotationBaseMode.Center
};
}
function createScene(divElement) {
const wellData = getWell3410A44();
const plot = new Plot({
"container": divElement,
"scale": new Vector3(1, 1, 2.2)
});
const data = new Trajectory3d(
wellData.xDeviation.slice(),
wellData.yDeviation.slice(),
wellData.elevation.slice(),
new Vector3(wellData.xRef, wellData.yRef, 0),
wellData.mds.slice(),
[new TrajectoryValue("data.dls", wellData.dls.slice())]
);
const components = new WellBoreData();
components.addComponent("path", { geometry: null });
const grid = new Grid({
"start": new Vector3(454589, 6780898, -2500),
"end": new Vector3(459324, 6784161, 0),
"modelstart": new Vector3(454589, 6780898, 2500),
"modelend": new Vector3(459324, 6784161, 0)
});
const gridPlanes = grid.getPlanes();
gridPlanes.back.visible = false;
const trajectory = new SchematicsNode({ "data": data, "components": components });
trajectory.setAnnotations([new AnnotationBase(
createAnnotation("Schematics Node, depth 1234")
)], [-1234]);
components.addComponent("path", {
"geometry": null,
"value": "data.dls",
"colorprovider": new DefaultColorProvider({
"values": [0, 1, 2],
"colors": ["green", "yellow", "red"]
})
});
components.addComponent("casing", {
"geometry": {
"depth": { "to": 270, "from": 40 },
"diameter": { "outer": 100 }
},
"opacity": 1,
"annotation": createAnnotation("Casing #1, depth from 40 to 270, diameter 100")
});
components.addComponent("casing", {
"geometry": {
"depth": { "to": 530, "from": 40 },
"diameter": { "outer": 75 }
},
"colorprovider": "blue",
"annotation": createAnnotation("Casing #2, depth from 40 to 530, diameter 75")
});
components.addComponent("casing", {
"geometry": {
"depth": { "to": 1075, "from": 40 },
"diameter": { "outer": 50 }
},
"annotation": createAnnotation("Casing #3, depth from 40 to 1075, diameter 50")
});
plot.getRoot().add(grid);
plot.getRoot().add(trajectory);
plot.fitCamera(ANGLED_CAMERA_DIRECTION, false, 0);
return plot;
}
function createLayoutFromString(string) {
switch (string) {
case "Default":
return new DefaultAnnotationLayout();
case "Expanded":
return new ExpandedAnnotationLayout();
}
throw new Error("Wrong annotation layout string");
}
function setLayout(plot, string) {
const layout = createLayoutFromString(string);
plot.getRoot().traverse((object) => {
if (!(object instanceof SchematicsNode))
return;
object.setAnnotationLayout(layout);
});
}
export { createScene, setLayout };
createScene(document.querySelector('[ref="plot"]'));
# Using Schematics
After the schematic is created, the built in functionality can be used to show/hide component types.
.cg-tooltip-holder {
position: relative;
}
.cg-tooltip {
position: absolute;
display: block;
padding: 2px 12px 3px 7px;
overflow: visible !important;
font-family: Roboto, Helvetica, Arial, sans-serif;
font-size: 13px;
background: white !important;
opacity: 0.9;
color: #333333;
border: solid 1px gray;
border-radius: 5px;
text-align: left;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
border-radius: 5px;
margin: 0 !important;
z-index: 10000;
max-width: 400px;
text-wrap: normal !important;
white-space: normal !important;
}
/* Default setting for tooltip */
.cg-tooltip-container {
position: absolute;
display: block;
overflow: visible !important;
font-family: Roboto, Helvetica, Arial, sans-serif;
font-size: 12px;
padding: 3px 7px;
background: #f7f7f7;
color: #333333;
border: 1px solid #938e8e;
opacity: 0.8;
text-align: left;
box-shadow: 3px 3px 10px #888;
margin: 0 !important;
z-index: 10000;
max-width: 400px;
text-wrap: normal !important;
white-space: normal !important;
user-select: none;
}
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
.cg-tooltip-container {
border-radius: 0;
}
}
/* Default left arrow for tooltip */
.cg-tooltip-arrow-left::before {
content: '';
position: absolute;
display: block;
width: 0px;
left: 0;
top: 50%;
border: 5px solid transparent;
border-left: 0;
border-right: 5px solid #938e8e;
transform: translate(calc(-100%), -50%);
}
.cg-tooltip-arrow-left::after {
content: '';
position: absolute;
display: block;
width: 0px;
left: 0;
top: 50%;
border: 4px solid transparent;
border-left: 0;
border-right: 4px solid #f7f7f7;
transform: translate(calc(-100%), -50%);
}
/* Default top arrow for tooltip */
.cg-tooltip-arrow-top::before {
content: '';
position: absolute;
display: block;
width: 0px;
left: 50%;
top: 0;
border: 5px solid transparent;
border-top: 0;
border-bottom: 5px solid #938e8e;
transform: translate(-50%, -100%);
}
.cg-tooltip-arrow-top::after {
content: '';
position: absolute;
display: block;
width: 0px;
left: 50%;
top: 0;
border: 4px solid transparent;
border-top: 0;
border-bottom: 4px solid #f7f7f7;
transform: translate(-50%, -100%);
}
/* Default right arrow for tooltip */
.cg-tooltip-arrow-right::before {
content: '';
position: absolute;
display: block;
width: 0px;
right: 0;
top: 50%;
border: 5px solid transparent;
border-right: 0;
border-left: 5px solid #938e8e;
transform: translate(100%, -50%);
}
.cg-tooltip-arrow-right::after {
content: '';
position: absolute;
display: block;
width: 0px;
right: 0;
top: 50%;
border: 4px solid transparent;
border-right: 0;
border-left: 4px solid #f7f7f7;
transform: translate(100%, -50%);
}
/* Default bottom arrow for tooltip */
.cg-tooltip-arrow-bottom::before {
content: '';
position: absolute;
display: block;
width: 0px;
left: 50%;
bottom: 0px;
border: 5px solid transparent;
border-bottom: 0;
border-top: 5px solid #938e8e;
transform: translate(-50%, 100%);
z-index: 10000;
}
.cg-tooltip-arrow-bottom::after {
content: '';
position: absolute;
display: block;
width: 0px;
left: 50%;
bottom: 0;
border: 4px solid transparent;
border-bottom: 0;
border-top: 4px solid #f7f7f7;
transform: translate(-50%, 100%);
z-index: 10000;
}
/* Tooltip item name */
/* Tooltip item value */
/* .cg-tooltip-item-value */
/* Tooltip item value */
.cg-tooltip-item-unit {
text-transform: none;
}
.cg-tooltip-item-name {
text-transform: capitalize;
white-space: nowrap;
vertical-align: middle;
font-size: 13px;
}
.cg-tooltip-row {
display: flex;
flex-direction: row;
align-items: center;
white-space: pre-wrap;
font-size: 12px;
line-height: 100%;
margin: 1px 0;
}
.cg-tooltip-title {
font-size: 13px;
height: 14px;
text-transform: capitalize;
}
.cg-tooltip-title .cg-tooltip-symbol {
margin-right: 0 !important;
}
.cg-tooltip-title-name {
vertical-align: middle;
}
.cg-tooltip-row + .cg-tooltip-row {
margin-top: 4px;
}
.cg-tooltip-row.cg-tooltip-title + .cg-tooltip-row {
margin-top: 5px;
}
.cg-tooltip-item-value + .cg-tooltip-item-unit {
margin-left: 1px;
}
/* Tooltip symbol */
.cg-tooltip-symbol-cell {
display: inline-flex;
min-width: 13px; /* 10px size + 3px margin */
}
.cg-tooltip-symbol {
margin-right: 3px;
background-color: transparent;
display: block;
}
.cg-tooltip-symbol > img {
display: block;
}
.cg-tooltip-list-cell {
display: inline-flex;
}
.cg-tooltip-list-symbol {
display: block;
margin-right: 3px;
width: 6px;
height: 6px;
vertical-align: middle;
border-radius: 50%;
border: 1px solid rgba(0,0,0,.4);
}
.cg-tooltip-symbol-legacy {
border-radius: 4px;
margin-right: 5px;
height: 8px;
width: 8px;
display: inline-block;
}
.cg-tooltip-title-legacy {
font-weight: 900;
}
/* Tooltip symbol circle */
.cg-tooltip-symbol.circle {
height: 10px;
width: 10px;
display: inline-block;
border-radius: 50%;
border: 1px solid rgba(0,0,0,.4);
}
/* Tooltip symbol line */
.cg-tooltip-symbol.line {
height: 10px;
width: 10px;
display: inline-block;
transform: scale(1.2, 0.2);
}
/* Tooltip symbol diamond */
.cg-tooltip-symbol.diamond {
height: 10px;
width: 10px;
display: inline-block;
transform: rotate(45deg);
border-radius: 0px;
}
/* Tooltip symbol square */
.cg-tooltip-symbol.square {
height: 10px;
width: 10px;
display: inline-block;
border-radius: 0px;
border: 1px solid rgba(0,0,0,.4);
}
# Multilateral Schematic
This example shows how to display a multilateral well trajectory using the 3D SchematicsNode.
import { Plot } from "@int/geotoolkit3d/Plot.ts";
import { Vector3 } from "/node_modules/.vite/deps/three.js?v=2235af65";
import { Trajectory3d } from "@int/geotoolkit3d/geodata/Trajectory3d.ts";
import { TrajectoryValue } from "@int/geotoolkit3d/geodata/TrajectoryValue.ts";
import { WellBoreData } from "@int/geotoolkit/schematics/data/WellBoreData.ts";
import { Grid } from "@int/geotoolkit3d/scene/grid/Grid.ts";
import { SchematicsNode } from "@int/geotoolkit3d/scene/schematics/SchematicsNode.ts";
import { AnnotationBase, Mode as AnnotationBaseMode } from "@int/geotoolkit3d/scene/AnnotationBase.ts";
import { DefaultColorProvider } from "@int/geotoolkit/util/DefaultColorProvider.ts";
import { MultiLatetalWells } from "/src/code/Carnac3D/helpers/multilateralwells.ts";
import { ExpandedAnnotationLayout } from "@int/geotoolkit3d/schematics/layout/ExpandedAnnotationLayout.ts";
const ANGLED_CAMERA_DIRECTION = new Vector3(0.5, -0.9, 0.1);
function createAnnotation(title) {
return {
"title": title,
"titlestyle": {
"font": "12px Arial",
"color": "yellow"
},
"linestyle": "yellow",
"mode": AnnotationBaseMode.Center
};
}
function createScene(divElement) {
const wellsData = MultiLatetalWells.getData();
const plot = new Plot({
"container": divElement,
"scale": new Vector3(1, 1, 2.2)
});
const schematicsNodeData = [];
wellsData.forEach((wellData) => {
const data = new Trajectory3d(
wellData.x.slice(),
wellData.y.slice(),
wellData.z.slice(),
new Vector3(wellData.xRef, wellData.yRef, 0),
null,
[
new TrajectoryValue("data.dls", wellData.values.slice())
]
);
const components2 = new WellBoreData();
components2.addComponent("path", {
"geometry": null,
"value": "data.dls",
"colorprovider": new DefaultColorProvider({
"values": [0, 1, 2],
"colors": ["green", "yellow", "red"]
})
});
schematicsNodeData.push({ "data": data, "components": components2 });
});
const grid = new Grid({
"start": new Vector3(-2e3, -2e3, -2500),
"end": new Vector3(2e3, 2e3, 0),
"modelstart": new Vector3(2e3, 2e3, 2500),
"modelend": new Vector3(-2e3, -2e3, 0)
});
const gridPlanes = grid.getPlanes();
gridPlanes.front.visible = false;
gridPlanes.back.visible = false;
const trajectory = new SchematicsNode(schematicsNodeData);
trajectory.setAnnotations([new AnnotationBase(
createAnnotation("Schematics Path #1, depth 1940")
)], [-1940], 0);
trajectory.setAnnotations([new AnnotationBase(
createAnnotation("Schematics Path #2, depth 1700")
)], [-1700], 1);
trajectory.setAnnotations([new AnnotationBase(
createAnnotation("Schematics Path #3, depth 1050")
)], [-1050], 2);
const components = schematicsNodeData[0]["components"];
components.addComponent("casing", {
"geometry": {
"depth": { "to": 270, "from": 40 },
"diameter": { "outer": 100 }
},
"opacity": 1,
"annotation": createAnnotation("Casing #1, depth from 40 to 270, diameter 100")
});
components.addComponent("casing", {
"geometry": {
"depth": { "to": 530, "from": 40 },
"diameter": { "outer": 75 }
},
"colorprovider": "blue",
"annotation": createAnnotation("Casing #2, depth from 40 to 530, diameter 75")
});
components.addComponent("casing", {
"geometry": {
"depth": { "to": 1900, "from": 40 },
"diameter": { "outer": 50 }
},
"annotation": createAnnotation("Casing #3, depth from 40 to 1900, diameter 50")
});
schematicsNodeData[1]["components"].addComponent("casing", {
"geometry": {
"depth": { "to": 1075, "from": 40 },
"diameter": { "outer": 50 }
},
"annotation": createAnnotation("Casing #4, depth from 40 to 1075, diameter 50")
});
schematicsNodeData[2]["components"].addComponent("casing", {
"geometry": {
"depth": { "to": 1075, "from": 40 },
"diameter": { "outer": 50 }
},
"annotation": createAnnotation("Casing #5, depth from 40 to 1075, diameter 50")
});
plot.getRoot().add(grid);
plot.getRoot().add(trajectory);
plot.fitCamera(ANGLED_CAMERA_DIRECTION);
trajectory.setAnnotationLayout(new ExpandedAnnotationLayout());
return plot;
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));