Wells on a map
Every well you may read in a project can have a location: the newest source you may read that locates it — a row of a table of wells, or a well-location file associated with the well. The location is never a second copy of the data: it is read from that source, in the CRS it was stored in, and converted only when you ask for another CRS. Each converted location records the conversion that was performed.
A table of wells in an approved database becomes located project wells in one journey: see Import wells from a table. Each imported well is located by its row of the kept copy.
In the Workspace, Map in the project’s sidebar draws the same wells and opens each one’s page: see Wells and wellbores.
Ophiolite serves the wells two ways:
- as JSON, from
entities/list(withbbox,bbox_crsandcrs) andentities/extent, and through the Python SDK’swells()andextent(); - as an OGC API – Features collection that GIS tools open directly:
/api/v1/projects/<project>/features/collections/wells/items.
Both apply the same rule: a location counts only when you may read its source, so a box never reveals a well you may not see, and a well whose source was withdrawn falls back to an older source you may read.
Fit the map to your wells
Section titled “Fit the map to your wells”Ask for the extent in CRS84 (longitude, latitude), which is what web maps use:
curl -s -X POST https://ophiolite.example/api/v1/projects/my-project/entities/extent \ -H "Authorization: Bearer $OPHIOLITE_ACCESS_KEY" -H 'Content-Type: application/json' \ -d '{"project_id": "my-project", "crs": "OGC:CRS84"}'# {"crs": "OGC:CRS84", "bbox": [5.12, 52.01, 6.93, 53.47], "count": 212, "untransformed": 0}untransformed counts wells whose stored CRS cannot be converted; they are left out of a converted
extent rather than placed wrongly.
Draw them with MapLibre
Section titled “Draw them with MapLibre”The OGC items response is GeoJSON in CRS84 by default, so MapLibre can use it as a source. Show each well’s name in its popup; keep identifiers, revisions and the source under a “Technical details” disclosure:
// Popup content: the well's name first; identifiers only under Technical details.function wellPopup(feature) { const p = feature.properties; const root = document.createElement("div"); const name = document.createElement("strong"); name.textContent = p.name; root.append(name); const details = document.createElement("details"); const summary = document.createElement("summary"); summary.textContent = "Technical details"; const list = document.createElement("dl"); for (const [label, value] of [["Well", p.entity_id], ["Source", p.source_asset_id], ["Revision", p.source_revision], ["Row", p.source_row]]) { if (!value) continue; const term = document.createElement("dt"); term.textContent = label; const text = document.createElement("dd"); text.textContent = value; list.append(term, text); } details.append(summary, list); root.append(details); return root;}
const base = "https://ophiolite.example/api/v1/projects/my-project";const headers = { Authorization: "Bearer " + accessKey };const extent = await (await fetch(base + "/entities/extent", { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ project_id: "my-project", crs: "OGC:CRS84" }) })).json();const wells = await (await fetch(base + "/features/collections/wells/items?limit=100", { headers })).json();
map.on("load", () => { map.addSource("wells", { type: "geojson", data: wells }); map.addLayer({ id: "wells", type: "circle", source: "wells", paint: { "circle-radius": 5, "circle-color": "#2f6f5e" } }); map.on("click", "wells", (e) => new maplibregl.Popup().setLngLat(e.lngLat).setDOMContent(wellPopup(e.features[0])).addTo(map)); if (extent.bbox) map.fitBounds([[extent.bbox[0], extent.bbox[1]], [extent.bbox[2], extent.bbox[3]]], { padding: 40 });});Follow the response’s next link for more than 100 wells. A well without a location you may read has a
null geometry and is not drawn.
Ask for the wells in a box
Section titled “Ask for the wells in a box”The JSON route needs the box’s CRS explicitly; nothing is guessed from the data:
curl -s -X POST https://ophiolite.example/api/v1/projects/my-project/entities/list \ -H "Authorization: Bearer $OPHIOLITE_ACCESS_KEY" -H 'Content-Type: application/json' \ -d '{"project_id": "my-project", "bbox": [150000, 460000, 170000, 480000], "bbox_crs": "EPSG:28992", "crs": "OGC:CRS84"}'The OGC route takes bbox and bbox-crs (default CRS84) and returns coordinates in crs (default CRS84),
each in its CRS’s axis order — EPSG:4326 is latitude first. The supported CRSs are CRS84, EPSG:4326,
EPSG:3857, RD New (EPSG:28992), UTM zone 31N (EPSG:32631) and ED50 / UTM zone 31N (EPSG:23031); another is refused
with 400.
In Python:
wells = client.wells(bbox=[150000, 460000, 170000, 480000], bbox_crs="EPSG:28992", crs="OGC:CRS84")wells.to_frame() # name, x, y, crs and the source of each locationwells.to_geojson() # needs the CRS84 fetch above; nothing is converted locallyclient.extent() # the same extent as entities/extentOpen them in QGIS
Section titled “Open them in QGIS”The same connection also opens tops, wellbore paths, point sets, lines and map features, and the plug-in opens surfaces: see Open your project in QGIS. This section covers the wells and the key.
Checked with QGIS 4.2.2. You need a project whose wells are located (see above) and a project access key with the read scope (account page › Project access keys).
Check the key first. Save it with the account page’s Copy button into a file, key.txt, and ask the server
whether it works, before you paste it anywhere (with your address and project id):
ophiolite doctor --online --url https://ophiolite.example --project my-project --key-file key.txtIt reports each step — address, TLS, credential arrived, credential accepted, project readable, wells — and stops at the first that fails, with what to do. If it removed a line break or spaces from the file it says so: QGIS needs the key without them. To put exactly the key on the clipboard, with no line break, run the line for your system:
tr -d '\r\n' < key.txt | xclip -selection clipboardtr -d '\r\n' < key.txt | pbcopyThe first is for Linux (X11; wl-copy instead of xclip -selection clipboard on Wayland), the second for macOS.
- On the account page, use the key’s Copy button (do not select the key by hand: a hand selection can carry a
line break). In QGIS open Settings › Options › Authentication, add a configuration of type API Header,
and add one header: name
Authorization, valueBearerand then paste the key right after the space, on the same line, with nothing after it (paste what the command above put on the clipboard). Give it a name you will recognise. If QGIS later reports that sign-in failed (401), see the table below. - Choose Layer › Add Layer › Add WFS / OGC API – Features Layer and create a new connection:
- URL:
https://ophiolite.example/api/v1/projects/my-project/features(your address and project id); - Version: OGC API – Features — choose it yourself, do not use Detect;
- Authentication: the configuration from step 1.
- URL:
- Connect, select the
wellscollection and add it. QGIS reads the description the landing page links, pages through the wells 100 at a time and asks for the part of the map you look at.
With more than 100 wells QGIS shows them in CRS84 unless you choose another CRS for the layer; a project whose visible wells share one stored CRS (and are at most 100) opens in that CRS. A well that has no location you may read appears in the attribute table without a point.
Coordinates are converted between the listed CRSs only; see Coordinates for how other data types are routed.
When QGIS does not load the wells
Section titled “When QGIS does not load the wells”QGIS saves a header value with a line break without complaint and then sends no Authorization header at all, so
the server sees no credential. Each refusal names its stage (stage in the answer, and “Last refused” beside “Last
used” on the account page); ophiolite doctor --online prints the same sentence.
| What you read | Stage | What to do |
|---|---|---|
| No credential reached Ophiolite; check the application’s authentication settings. (QGIS: the layer is not valid, 401) | no-credential | The key did not arrive. Most often the value in QGIS’s API Header ends with a line break: open the configuration, delete the value and paste it again with the command above (Bearer , one space, the key, nothing after it). |
| The Authorization header is not a bearer credential of a kind Ophiolite accepts. | malformed-credential | The value is not Bearer and one key: check the scheme is Bearer, there is one space, and no space or line break inside the key. |
| Project access key revoked or expired | credential-refused | The key is unknown, expired or removed: create a new key on the account page and put it in the application. |
| Project access key scope denied, or Owner no longer has this access | access-denied | The key works but not for this: use a key for this project with the scope you need, or ask a project administrator. |
cannot reach the server: check Tailscale (from ophiolite doctor --online) | address or tls | The deployment is private to its tailnet: connect Tailscale on this computer and try again. |
