<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mezux</title>
    <description>The latest articles on DEV Community by Mezux (@mezux).</description>
    <link>https://dev.to/mezux</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4210101%2Fc1f6a3a2-582d-4410-be69-6b3bdbf0b7da.png</url>
      <title>DEV Community: Mezux</title>
      <link>https://dev.to/mezux</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mezux"/>
    <language>en</language>
    <item>
      <title>How Ground Penetrating Radar Finds Buried Cables: The Physics and Maths, in Python</title>
      <dc:creator>Mezux</dc:creator>
      <pubDate>Tue, 27 Oct 2026 07:52:26 +0000</pubDate>
      <link>https://dev.to/mezux/how-ground-penetrating-radar-finds-buried-cables-the-physics-and-maths-in-python-c7j</link>
      <guid>https://dev.to/mezux/how-ground-penetrating-radar-finds-buried-cables-the-physics-and-maths-in-python-c7j</guid>
      <description>&lt;p&gt;Every time a new fibre line is laid in India, someone has to answer a simple question before digging: what is already down there?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ayubx11i8onnzx80uwa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ayubx11i8onnzx80uwa.png" alt=" " width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit a live power cable and someone can get hurt. Cut an existing optical fibre cable (OFC) and a whole area can lose internet. Hit a gas line and it's worse. The trouble is that old utility records are often missing or wrong.&lt;/p&gt;

&lt;p&gt;At &lt;a href="//mezux.in"&gt;our company&lt;/a&gt; we answer that question with &lt;strong&gt;Ground Penetrating Radar (GPR)&lt;/strong&gt;. It's mostly physics and a little maths, and you can understand it with about 30 lines of Python. Here's how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What GPR actually does
&lt;/h2&gt;

&lt;p&gt;A GPR unit looks like a lawnmower. It's pushed along the road and works in three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An antenna sends a short radar pulse into the ground.&lt;/li&gt;
&lt;li&gt;When the pulse hits something with different electrical properties (a pipe, a cable, a duct, a void), part of it bounces back.&lt;/li&gt;
&lt;li&gt;The unit records how long the echo took to come back, in nanoseconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do that every few centimetres along a line and you get a radargram: a picture of the ground's cross-section.&lt;/p&gt;

&lt;p&gt;The catch: the radar doesn't measure depth. It measures time. Turning time into depth is where the physics comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. From echo time to depth
&lt;/h2&gt;

&lt;p&gt;Radar travels slower in soil than in air. How much slower depends on the soil's relative permittivity (εr, also called the dielectric constant):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;v = c / √εr&lt;/li&gt;
&lt;li&gt;c is the speed of light, about 0.3 m per nanosecond.&lt;/li&gt;
&lt;li&gt;Dry sand has a low εr, so the wave stays fast.&lt;/li&gt;
&lt;li&gt;Wet clay has a high εr, so the wave is slow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pulse goes down and back up, so depth is half the distance travelled:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
import math&lt;/p&gt;

&lt;p&gt;C = 0.2998  # speed of light, metres per nanosecond&lt;/p&gt;

&lt;p&gt;def velocity(eps_r: float) -&amp;gt; float:&lt;br&gt;
    """Radar wave speed in the ground (m/ns) from relative permittivity."""&lt;br&gt;
    return C / math.sqrt(eps_r)&lt;/p&gt;

&lt;p&gt;def depth(two_way_time_ns: float, v: float) -&amp;gt; float:&lt;br&gt;
    """Depth in metres: the wave goes down AND back up, so halve it."""&lt;br&gt;
    return v * two_way_time_ns / 2&lt;/p&gt;

&lt;p&gt;for soil, eps in [("dry sand", 4), ("moist loam", 12), ("wet clay", 25)]:&lt;br&gt;
    v = velocity(eps)&lt;br&gt;
    print(f"{soil:&amp;lt;11} εr={eps:&amp;gt;2}  v={v:.3f} m/ns  20 ns echo → {depth(20, v):.2f} m deep")&lt;/p&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;dry sand    εr= 4  v=0.150 m/ns  20 ns echo → 1.50 m deep&lt;br&gt;
moist loam  εr=12  v=0.087 m/ns  20 ns echo → 0.87 m deep&lt;br&gt;
wet clay    εr=25  v=0.060 m/ns  20 ns echo → 0.60 m deep&lt;/p&gt;

&lt;p&gt;The same 20 ns echo can mean 60 cm or 1.5 m depending on the soil. If you guess the soil wrong, every depth in your report is wrong. So how do you get the real velocity?&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The hyperbola trick: calibrating on site
&lt;/h2&gt;

&lt;p&gt;This is my favourite part.&lt;/p&gt;

&lt;p&gt;When the GPR cart passes over a pipe that runs across its path, the radar "sees" the pipe before it reaches it and after it has passed, because the beam spreads out like a cone. The echo takes longest when the pipe is off to the side and shortest when the cart is directly on top.&lt;/p&gt;

&lt;p&gt;On the radargram that draws a hyperbola, an upside-down "U". Its shape follows this equation:&lt;/p&gt;

&lt;p&gt;t(x) = (2 / v) · √( d² + (x − x₀)² )&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;x₀ is where the cart was directly above the pipe.&lt;/li&gt;
&lt;li&gt;d is the pipe's depth.&lt;/li&gt;
&lt;li&gt;v is the wave speed in this exact soil, today.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Square both sides and it becomes a straight line:&lt;/p&gt;

&lt;p&gt;t² = (4 / v²) · (x − x₀)²  +  (2d / v)²&lt;/p&gt;

&lt;p&gt;So a basic linear fit gives you both the velocity and the depth:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
import numpy as np&lt;/p&gt;

&lt;h1&gt;
  
  
  Simulated field data: a pipe 1.2 m deep, true velocity 0.10 m/ns
&lt;/h1&gt;

&lt;p&gt;rng = np.random.default_rng(1)&lt;br&gt;
true_v, true_d, x0 = 0.10, 1.2, 5.0&lt;br&gt;
x = np.linspace(3.5, 6.5, 31)                       # cart position (m)&lt;br&gt;
t = 2 * np.sqrt(true_d*&lt;em&gt;2 + (x - x0)&lt;/em&gt;*2) / true_v   # echo time (ns)&lt;br&gt;
t += rng.normal(0, 0.3, t.size)                     # add measurement noise&lt;/p&gt;

&lt;h1&gt;
  
  
  Fit t² against (x − x0)²
&lt;/h1&gt;

&lt;p&gt;slope, intercept = np.polyfit((x - x0)&lt;strong&gt;2, t&lt;/strong&gt;2, 1)&lt;br&gt;
v_est = 2 / np.sqrt(slope)&lt;br&gt;
d_est = v_est * np.sqrt(intercept) / 2&lt;/p&gt;

&lt;p&gt;print(f"velocity ≈ {v_est:.3f} m/ns, depth ≈ {d_est:.2f} m")&lt;/p&gt;

&lt;h1&gt;
  
  
  velocity ≈ 0.099 m/ns, depth ≈ 1.19 m
&lt;/h1&gt;

&lt;p&gt;That's why good survey teams calibrate velocity on site instead of using a value from a table. The soil itself tells you its speed, and every buried pipe is a free calibration point.&lt;/p&gt;

&lt;p&gt;Simplification alert: real processing also accounts for the distance between transmitter and receiver, the pipe's radius, and the soil changing along the route. But the core idea is exactly this fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Choosing a frequency: see deep, or see detail?
&lt;/h2&gt;

&lt;p&gt;GPR antennas come in different frequencies, and there's a trade-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low frequency (100–250 MHz): reaches deeper but shows less detail.&lt;/li&gt;
&lt;li&gt;Mid frequency (250–400 MHz): the usual choice for utilities at normal burial depths.&lt;/li&gt;
&lt;li&gt;High frequency (1.6–2.6 GHz): fine detail but shallow. Used for scanning concrete slabs and rebar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A rule of thumb: the smallest vertical detail you can separate is about a quarter of the wavelength.&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
def resolution_cm(freq_mhz: float, v: float) -&amp;gt; float:&lt;br&gt;
    wavelength_m = v / (freq_mhz / 1000)   # v in m/ns, f in GHz&lt;br&gt;
    return wavelength_m / 4 * 100&lt;/p&gt;

&lt;p&gt;v = velocity(9)   # a typical moist soil&lt;br&gt;
for f in (200, 400, 2000):&lt;br&gt;
    print(f"{f:&amp;gt;5} MHz → ~{resolution_cm(f, v):.1f} cm vertical resolution")&lt;br&gt;
  200 MHz → ~12.5 cm vertical resolution&lt;br&gt;
  400 MHz → ~6.2 cm vertical resolution&lt;br&gt;
 2000 MHz → ~1.2 cm vertical resolution&lt;/p&gt;

&lt;p&gt;Higher frequency gives sharper detail, but the signal also dies faster in the ground. So you pick the antenna for the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Where GPR struggles (and why honest reports matter)
&lt;/h2&gt;

&lt;p&gt;GPR is not magic, and anyone who says it finds everything is selling something.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wet clay and salty soil conduct electricity and soak up the radar signal. Depth reach drops sharply.&lt;/li&gt;
&lt;li&gt;Small targets with low contrast can be hard to see, such as a thin cable in soil with similar properties.&lt;/li&gt;
&lt;li&gt;Non-metallic pipes (HDPE, PVC, empty ducts) can be found by GPR. That's a big advantage over metal detectors. But it depends on soil, size and depth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why a good survey:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;also uses electromagnetic locators for metal cables;&lt;/li&gt;
&lt;li&gt;gives a confidence grade for each detected line (quality levels in the style of PAS 128);&lt;/li&gt;
&lt;li&gt;records no-access areas and limitations in the report;&lt;/li&gt;
&lt;li&gt;recommends a trial pit at critical crossings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. What this looks like in the field
&lt;/h2&gt;

&lt;p&gt;On a typical &lt;a href="//mezux.in"&gt;fibre-route survey&lt;/a&gt; along a highway, our team:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;agrees a scan plan before going out;&lt;/li&gt;
&lt;li&gt;runs parallel lines along the planned cable route;&lt;/li&gt;
&lt;li&gt;adds cross-lines at roads, drains, canals and railway crossings, where surprises usually are;&lt;/li&gt;
&lt;li&gt;calibrates velocity from hyperbolas, as above;&lt;/li&gt;
&lt;li&gt;marks the services on the ground;&lt;/li&gt;
&lt;li&gt;has a second engineer, who didn't collect the data, review it the same day;&lt;/li&gt;
&lt;li&gt;delivers a signed report with depth by section, plus CAD/GIS files and an L-section depth profile where required.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The raw radar data is archived, so every line on the drawing can be traced back to the echo that produced it. For the developers reading this: think of it as version control for the underground.&lt;/p&gt;

&lt;p&gt;Try it yourself&lt;/p&gt;

&lt;p&gt;If you want to play with real radar data, look up open-source GPR tools in Python (for example, search "GPR processing Python" on GitHub). Start by fitting a hyperbola from a published sample radargram. It's a satisfying weekend project, and you'll never look at a dug-up road the same way again.&lt;/p&gt;

&lt;p&gt;I'm Manas Verma, from &lt;a href="//mezux.in"&gt;Mezux Touch&lt;/a&gt;, a GPR survey and underground utility mapping company based in Faridabad, India. Questions about GPR or utility mapping are welcome in the comments.&lt;/p&gt;

</description>
      <category>gpr</category>
      <category>survey</category>
      <category>groundpenetratingradar</category>
      <category>gprsurvey</category>
    </item>
  </channel>
</rss>
