In offshore petroleum exploration practice, the coordinates of blocks designed by geologists for a particular purpose must be provided in Degree-Minute-Second format: the degree uses 2 or 3 digits, the minute uses 2 digits, and the second uses 2 digits, in one of four values — 00, 15, 30 or 45 (in increments of a quarter of a minute). All coordinates must be listed in clockwise order, starting from the name vertex (the most northeast vertex). Such a block is called a “standard block”.
Designing blocks is daily work in the resource exploration industry. Blocks serve many purposes, including but not limited to administration (relinquishing part of a block, dissolving several blocks into one), block bid campaigns, and so on.
Geologists usually draft blocks on a projected geological setting basemap. Unfortunately, the mapping software in use has no snap functionality. It can only export polygons (the drafted blocks) in projected XY format. It can, however, display a geodetic reference grid at 15-second intervals.
A new workflow was designed to handle the blocks:
Geologists design blocks whose vertices are visually, but not precisely, on the nodes of a 15-second geodetic reference grid.
All blocks reside in a layer, with valid identifiers.
Export the design layer of blocks in projected format as a text file.
Write a program to read the file and generate standard blocks.
3. Implementation
The implementation is a Python package called “transco” (TRANSformation of COordinates), written in 2022 and in use since then.
transco uses pyproj to perform the transformation. pyproj is a Python interface to PROJ. Shapely is also used. Shapely is a Python wrapper for GEOS.
3.1 class
transco uses three classes to represent the data: Layer, Block and Point. Points are properties of a Block instance, and blocks are properties of a Layer instance.
classBlock: """A raw block resides in a Layer object."""
def__init__(self, layer, block): self.layer = layer self.location = self.layer.location self.order = 0 self.crs = self.layer.crs self.transformer = self.layer.transformer name_str = block[0][0] try: name, order = name_str.split("--") order = int(order) if name.strip(): self.name = name.strip().replace("/", "").replace("\\", "") if order >= 1: self.order = order except: self.name = name_str.rstrip("--").strip().replace("/", "").replace("\\", "") ifnot self.name: raise Exception("Failed to create a name for this Block") self._points = [] for p in block: point = Point(self, p) self._points.append(point) self.__regular_points() self.__unparallel_points() self.get_code_name()
@property defpoints(self): return self._points
The block object gets CRS information from its Layer object.
As before, a Point instance gets its CRS from its Block object.
3.2 Function
The basic idea is that the original XY coordinates are transformed into degrees and then snapped to the nearest geodetic reference grid node by the _offset method.
Several methods modify the blocks: rotating points if they are not clockwise, removing redundant points, checking for errors, computing block names by spatial relationship, and so on.
defd2dms(x): """ Transform a single float degree value to DMS format. return a tuple of degree, minute, second in float. >>> d2dms(113.5) (113.0, 30.0, 0.0) >>> d2dms(20.254166667) (20.0, 15.0, 15.000001199999247) """ d_decimal, d = math.modf(x) m_decimal, m = math.modf(d_decimal * 60) s = m_decimal * 60 return d, m, s
The last step is defining functions to call methods on the objects.
defavailable_crs(defined_crs): all_crs = {} for key, value in defined_crs.items(): if value: all_crs[key] = value return all_crs
defshow_help(**kw): print(__desc__) print("Version {}".format(__version__)) print("Author: ", __author__) print(__transco_help__) location = kw.get("location", None) if location: try: crs = get_crs("proj", "wgs84utm", location) except: print("Failed to get CRS.") raise if crs: print("Current CRS:") print(crs.name) print("Area of Use:") print(crs.area_of_use)
defmain(): parser = argparse.ArgumentParser(description=__desc__) parser.add_argument( "file", help="the input file, default is 'df_design.txt'.", nargs="?" ) parser.add_argument( "location", help="the location for projection selection, default is 'location'.", nargs="?", ) parser.add_argument( "-a", "--all", action="store_true", help="save all output files" ) parser.add_argument("--version", action="store_true", help="show version") parser.add_argument("--allcrs", action="store_true", help="show all available CRS") args = parser.parse_args() try: ...
After that, I can call the main function from the CLI, call the functions and classes directly in an interactive Python environment, or use it in a Jupyter notebook.
4. Results
transco has been in use since 2022. A 50-block batch that previously took roughly an hour per block to prepare by hand in Excel now runs in about 10 minutes.
The larger gain is in the revision loop. Geologists revise their block designs repeatedly before a final version is agreed, and the manual cost was paid again on every revision. Re-running transco costs nothing, so a design can be iterated as many times as the work requires.