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.
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) and UTM zone 31N (EPSG:32631); 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”In QGIS choose Layer › Add Layer › Add WFS / OGC API – Features Layer, create a connection to
https://ophiolite.example/api/v1/projects/my-project/features with your access key as a bearer
token, and add the wells collection.
Coordinates are converted between the listed CRSs only; see Coordinates for how other data types are routed.
