Monday, August 18, 2025

Using custom Hausa font within QGIS

 QGIS just like most computer software uses the fonts that are installed on your computer. So, to use a Hausa font in QGIS you will need to install the font on your computer.

There are two commly used Hausa font in use, namely: Dr. Abdallah Uba Adamu (Abdalla.ttf) and Rabiat Muhammad (Rabiat.ttf) fonts. You can download and install both from this Google drive folder. After you download double-click on the file to and click on install button to install them on your computer as seen below.




Both fonts where developed by Prof. Abdalla Uba Adamu in his contribution to the development of Hausa language in modern era, he developed 'hooked' Hausa language character font sets (ɓ Ɓ ɗ Ɗ ƙ Ƙ), which were not present at the advent of the word processors in Nigeria in the 1990s. He named them 'rabi'at' (after his mother, Rabi'at Muhammad) and 'abdalla' (after himself, as he could not think of any name at the time).



After installation, the fonts will be available in QGIS as seen above.

To type the special Hausa characters (ɓ Ɓ ɗ Ɗ ƙ Ƙ), we will utilize "~, |, ], [, } and {" for Abdalla.ttf font and "v, V, x, X, q and Q" for Rabiat.ttf font. The table below summaries what you need to type to achieve keying or typing the special Hausa characters.








That is it!

Monday, June 30, 2025

Introduction to G.Projector 3 — A Map Projection Explorer by NASA

 National Aeronautics and Space Administration (NASA) has this great tool called G.Projector for exploring complex Map Projection not easily implemented in common cartographic or GIS tool. The software is based o Java and it is available on major operating system like Windows, macOS and Linux. G.Projector is an interactive tool for exploring map projections. It takes an input map image, typically in Equirectangular form (also called Cylindrical Equidistant), and transforms it to one of over 200 other projections.


Download and launch G.Projector from the official website, it is a simple standalone software (no installation is required), however you need to have Java 11 or higher installed on your machine.

To start using G.Projector, you need to have a basemap (source map) of the globe in one of the following projections namely: Aitoff, Cylindrical equal Area, Equirectangular, Hammer, Robinson, or Winkel Tripel. Your source map in any of the projections above can then be transformed to one of over 200 other projections. An important note is that your input map image, must not have a border or extra margins around the edges.

A KMZ file ca also be used as an input source map.

With this tool, you can take a globe map like the once below and convert them into many types of map projections.






The steps required are as follow;-

1) Launch the G.Projector software

2) Go to "File" >> "New with import…", select your base map and choose its corresponding map projection such as "Equiretangular" as "Input projection"


3) In the "Projection" tab, select the new map projection type you want to transform the map into. As an example, here I choose "Sinusoidal" as projection mode with Interrupted: 30° Gores

4) In the "Graticule" tab, set the "Stroke" as you want

5) In the "Overlays" tab, select <None> for "Overlay 1".

6) In the "Border" tab, set "Weight" to 0%.

7) Export the current view now under "File" >> "Save Map as…"


A note on Gore
In cartography, a gore is a wedge-shaped segment of a map, often resembling the area between two lines of longitude, that can be fitted to a globe with minimal distortion. It can also refer to a triangular piece of land resulting from conflicting surveys or a gap between boundaries. 

1. Globe Gores:
A gore is a section of a map designed to be wrapped around a globe. 
These sections are typically shaped like a wedge, with the wider end at the top (near the equator) and the narrower end at the bottom (near the pole). 
The term "gore" in this context comes from the shape of the segment, which is similar to the panels of a hot air balloon or parachute. 

2. Land Gores:
In surveying and land ownership, a gore refers to a small, usually triangular, piece of land that is not included in any adjacent property or land survey.
These gaps can arise from errors in surveying, where two or more surveys fail to align properly, or from discrepancies in boundaries.
For example, the "g<bos> land" between the Philipse Patent and the Rombout Patent in New York was a well-known gore. 

3. Distinguishing Gores from Gaps:
While both gores and gaps represent areas not included in adjacent properties, a gore is generally triangular in shape, while a gap can be any irregular shape.
Gores often arise from conflicting survey lines, whereas gaps may result from errors in descriptions of property boundaries. 

That is it!

Happy Map Projection

Monday, June 9, 2025

How to Manually Install WebODM on local server in Windows 11

 There are several ways to install the WebODM server software including purchasing the windows installer for easy installation. In this guide, I will demonstrate how it can be install on windows 11 local machine, for other detailed installation guide see the webODM github page.


Photogrammetry software require a computer with high processing power and the minimum recommend spec for webODM are 100GB free disk space and 16GB RAM.

Saturday, May 31, 2025

Convert any picture into svg vector graphics

 The vtracer module can be used to convert a raster image to vector SVG image. See the code below;-  

import vtracer # pip install vtracer

inp = "map.png"
out = '001'

# Minimal example: use all default values, generate a multicolor SVG
vtracer.convert_image_to_svg_py(inp, f'{out}_defaultcolor.svg')

# Single-color example. Good for line art, and much faster than full color:
vtracer.convert_image_to_svg_py(inp, f'{out}_blackandwhite.svg', colormode='binary')

print('Done...')

Original Raster Map Image




Resulting Vector SVG Map 



Thursday, April 24, 2025

Script to Create New Layers for Survey Plan Drawing in AutoCAD

 







LAYER NEW BorderLine,Beacons,BoundaryLine,Title,NorthArrow,ContourLine,Legend,Roads
COLOR 7 Beacons
COLOR 10 BoundaryLine
COLOR 7 Title
COLOR 5 NorthArrow
COLOR 12 ContourLine
COLOR 7 BorderLine


(alert "DONE CREATING THE LAYERS...")  



To list all the layers' names in the current drawing:-

(defun c:ListLayersSimple (/ tbl)
  (setq tbl (tblnext "LAYER" T)) ; Start of layer table
  (while tbl
    (princ (strcat "\n" (cdr (assoc 2 tbl)))) ; Layer name
    (setq tbl (tblnext "LAYER")) ; Next layer
  )
  (princ)
)


Thank you for reading.

Thursday, April 17, 2025

Setting AutoCAD drawing UNITS using AutoLISP

Program to configure AutoCAD drawing UNITS for Survey Plan Drawing

 One important ritual you must perform whenever you are starting a new drawing in AutoCAD, is to setup the drawing UNITS according to your drawing needs or specification.

For survey drawings, you will make not less than ten clicks to complete the process. So, I decided to automate this process using AutoLISP.


The UNITS settings are available via the following AutoCAD system variable commands:-

  • LUNITS -  system variable that controls the linear/length drawing units
  • LUPREC -  system variable that controls the linear/length precision
  • AUNITS -  system variable that controls the angles drawing units
  • AUPREC -  system variable that controls the angles precision
  • ANGBASE -  system variable that controls the angle orientation
  • ANGDIR -  system variable that controls the angle direction to be either Clock-clockwise or Counter-clockwise
  • INSUNITS - system variable that controls the insertion scaling

The picture above show what each variable is found.

To get the default values you want to use, use the getvar function in AutoLISP to confirm the values you want to set. The values can be checked as follow:-

(getvar "LUNITS")
(getvar "LUPREC")
(getvar "AUNITS")
(getvar "AUPREC")
(getvar "ANGDIR")
(getvar "ANGBASE")
(getvar "INSUNITS")

The setvar function in AutoLISP to set the drawing units in AutoCAD. For example, running this expression below will set the linear/length drawing units to Decimal units.

(setvar "LUNITS" 2)

The Unit format is as follow: 1=Scientific, 2=Decimal, 3=Engineering, 4=Architectural, 5=Fractional

To set the linear/length precision,the expression is like this below which will set the precision to four decimal places: (0.0001). To change the decimal place, change the value 4 to a new number.

(setvar "LUPREC" 4)

Similar concept is applicable to the other units system variables above. For AUNITS, it requires an integer between 0 and 4 and AUPREC requires an integer between 0 and 8.

(setvar "AUNITS" 1)
(setvar "AUPREC" 1)

ANGDIR requires 0 or 1 only. ANGBASE can be set to any number but in this case, I will use 270 to set it to NORTH.

(setvar "ANGDIR" 1)
(setvar "ANGBASE" 270)

For the insertion scaling, I will 6 for meters.

(setvar "INSUNITS" 6)


Putting it altogether:-


;;;Program to configure AutoCAD drawing UNITS for Survey Plan Drawing

;;;Step 1: Manually configure the settings you wanted

;;;Step 2: Determine the default setting using the GETVAR SYSVariable

(defun C:GETDEFAULTUNITS(/ linear-units linear-units-precision angular-units angular-units-precision angule-dir base-angle insertion-scale)
	(setq linear-units (getvar "LUNITS") ; 2
	      linear-units-precision (getvar "LUPREC") ; 2
	      angular-units (getvar "AUNITS") ; 1
	      angular-units-precision (getvar "AUPREC") ; 1
	      angule-dir (getvar "ANGDIR") ; 1
	      base-angle (getvar "ANGBASE") ; 270
	      insertion-scale (getvar "INSUNITS") ; 6
	  ) ; end setq
	
	(princ)
) ; end defun



;;; Step 3: Configure the units using the values obtained above
(defun C:SETUNITS(/)

	(setvar "LUNITS" linear-units)
	(setvar "LUPREC" linear-units-precision)
	(setvar "AUNITS" angular-units)
	(setvar "AUPREC" angular-units-precision)
	(setvar "ANGDIR" angule-dir)
	(setvar "ANGBASE" base-angle)
	(setvar "INSUNITS" insertion-scale)

	(princ)
); end defun



That is it!

Friday, February 28, 2025

Calculate bearing of lines in QGIS

 Bearing refers to the direction or orientation of a line or object relative to a fixed reference point, typically north. It is often measured in degrees, with 0° representing north, 90° representing east, 180° representing south, and 270° representing west. In simpler terms, bearing is like a compass pointing in a specific direction.

Bearings are commonly used in the field of Navigation, Surveying, Astronomy and Engineering. In some instances, different discipline use different terms such as Azimuth, Heading, Direction, Course etc to refer to bearing.

As at the time of writing, calculating the bearing of a line in QGIS doesn't work well for the context of surveying in Nigeria. Nigerian land surveyors work with Whole Circle Bearing (WCB) which is a method of expressing direction using degrees measured clockwise from north. WCB values range from 0° to 360° where North is 0°, East is 90°, South is 180° and West is 270°.



In QGIS under the "Measure" tool/icon, you will find "Measure Bearing" tool. Currently, the bearing is in Quadrant format.

In this post, lets explore how to calculate the Whole Circle Bearing of lines and use it to label the lines.

Saturday, February 22, 2025

Electronic Nautical Charts in QGIS

 Different organizations are responsible for the production of Electronic Navigational Charts a.k.a Electronic Nautical Charts in different countries. In Nigeria, the Nigerian Navy Hydrographic Office (NNHO) commenced the production of Nautical Charts in 2018.

In the US, charts are produced by National Oceanic and Atmospheric Administration (NOAA). In the UK, it is UK Hydrographic Office (UKHO), they called their charts 'Admiralty Charts'. Other countries have similar agencies and organizations that makes these charts.

Most of these organizations have provided access to the charts in WMS/WMTS layers in QGIS software. In this post, I will be using the service provided by ArgoGIS, a marine geographic information based on nautical charts as web map service.

The ArgoGIS WMS url is: https://enc.argogis.com/wms/wms_enc

To add it to QGIS, navigate to the Layer menu and add WMS/WMTS layer, then provide the url above. The result around 'Bonny Town' looks like the image below.


These charts are used by oceanographers, mariners, kayakers, navigators, boat captains, commercial fishermen, nautical tourists, coastal engineers, marine scientists, hydrographic surveyors (hydrographers) etc to plan voyages and navigate ships safely by providing detailed information about water depths, locations of navigational hazards like rocks and shoals, positions of aids to navigation, anchorages, coastlines, and other features essential for safe navigation on waterways (in streams, rivers, lakes, oceans, seas, and coastal areas), particularly in coastal areas and open seas.

Happy navigaton!

Friday, February 14, 2025

AutoLISP Program that iterates through selected lines, calculates their angles and lengths, and then annotates them

 In other words, the autoLISP program will select a line, get its bearing and distance and write the values on beside the line. The distance is in meters and the bearing format is in Whole Circle Bearing and DD°MM'SS


(defun c:BRGDIST (/ ss i entData ptStart ptEnd dx dy angleRad angleDeg wcb bearingStr dist midpoint)
  ; Function to convert decimal degrees to DMS (degrees, minutes, seconds)
  (defun DEGtoDMS (deg / d m s)
    (setq d (fix deg)) ; Degrees
    (setq m (fix (* (- deg d) 60))) ; Minutes
    (setq s (* (- (* (- deg d) 60) m) 60)) ; Seconds
    (strcat (itoa d) "°" (itoa m) "'" (rtos s 2 2) "\"") ; Format as DD°MM'SS"
  )

  ; Select lines
  (setq ss (ssget '((0 . "LINE"))))
  (if ss
    (progn
      (setq i 0)
      (repeat (sslength ss)
        ; Extract line data
        (setq entData (entget (ssname ss i)))
        (setq ptStart (cdr (assoc 10 entData))) ; Start point
        (setq ptEnd (cdr (assoc 11 entData)))   ; End point
        
        ; Calculate bearing
        (setq angleRad (angle ptStart ptEnd)) ; Angle in radians (from X-axis, counterclockwise)
        (setq angleDeg (/ (* angleRad 180.0) pi)) ; Convert to degrees
        
        ; Convert to Whole Circle Bearing (clockwise from north, 0°-360°)
        (setq wcb (rem (+ (- 90 angleDeg) 360) 360)) ; Adjust for WCB
        (setq bearingStr (DEGtoDMS wcb)) ; Convert WCB to DMS format
        
        ; Calculate distance
        (setq dist (rtos (distance ptStart ptEnd) 2 2)) ; Distance with 2 decimal places
        
        ; Calculate midpoint
        (setq midpoint (list
          (/ (+ (car ptStart) (car ptEnd)) 2.0)
          (/ (+ (cadr ptStart) (cadr ptEnd)) 2.0)
        ))
        
        ; Create text at midpoint
        (entmake
          (list
            '(0 . "TEXT")
            (cons 10 midpoint)          ; Insertion point
            (cons 40 5)               ; Text height
            (cons 1 (strcat bearingStr "      " dist"m")) ; Bearing (DMS) + distance
            (cons 50 angleRad)          ; Rotation angle (aligned with line)
            (cons 7 "Standard")         ; Text style
          )
        )
        (setq i (1+ i))
      )
    )
  )
  (princ)
)


This will create a new layer for the annotation

(defun c:BRGDIST (/ ss i entData ptStart ptEnd dx dy angleRad angleDeg wcb bearingStr dist midpoint)
  ; Function to convert decimal degrees to DMS (degrees, minutes, seconds)
  (defun DEGtoDMS (deg / d m s)
    (setq d (fix deg)) ; Degrees
    (setq m (fix (* (- deg d) 60))) ; Minutes
    (strcat (itoa d) "°" (itoa m) "'") ; Format as DD° MM'
    ;; (setq s (* (- (* (- deg d) 60) m) 60)) ; Seconds
    ;; (strcat (itoa d) "°" (itoa m) "'" (rtos s 2 2) "\"") ; Format as DD° MM' SS"
  )

  ; Create or use the annotation layer
  (if (not (tblsearch "LAYER" "BRG_DIST"))
    (command "._LAYER" "_M" "BRG_DIST" "_C" "1" "" "") ; Create layer and set color to red
  )
  (setvar "CLAYER" "BRG_DIST") ; Set current layer to BRG_DIST

  ; Select lines
  (setq ss (ssget '((0 . "LINE"))))
  (if ss
    (progn
      (setq i 0)
      (repeat (sslength ss)
        ; Extract line data
        (setq entData (entget (ssname ss i)))
        (setq ptStart (cdr (assoc 10 entData))) ; Start point
        (setq ptEnd (cdr (assoc 11 entData)))   ; End point
        
        ; Calculate bearing
        (setq angleRad (angle ptStart ptEnd)) ; Angle in radians (from X-axis, counterclockwise)
        (setq angleDeg (/ (* angleRad 180.0) pi)) ; Convert to degrees
        
        ; Convert to Whole Circle Bearing (clockwise from north, 0°-360°)
        (setq wcb (rem (+ (- 90 angleDeg) 360) 360)) ; Adjust for WCB
        (setq bearingStr (DEGtoDMS wcb)) ; Convert WCB to DMS format
        
        ; Calculate distance
        (setq dist (rtos (distance ptStart ptEnd) 2 2)) ; Distance with 2 decimal places
        
        ; Calculate midpoint
        (setq midpoint (list
          (/ (+ (car ptStart) (car ptEnd)) 2.0)
          (/ (+ (cadr ptStart) (cadr ptEnd)) 2.0)
        ))
        
        ; Create text at midpoint
        (entmake
          (list
            '(0 . "TEXT")
            (cons 10 midpoint)          ; Insertion point
            (cons 40 5)               ; Text height
            (cons 1 (strcat bearingStr "   " dist "m")) ; Bearing (DMS) + distance
            (cons 50 angleRad)          ; Rotation angle (aligned with line)
            (cons 7 "Standard")         ; Text style
            (cons 8 "BRG_DIST")         ; Layer
          )
        )
        (setq i (1+ i))
      )
    )
  )
  (princ)
)

Happy Coding!

Thursday, February 6, 2025

Generating AutoCAD Script (.SCR) file from GPS/GNSS Data

 Recently, I was working on a project where I had to label some points in several AutoCAD files from data points obatined from GPS/GNSS reciever. So, I wrote this Python code to 'Generate AutoCAD Script (.SCR) file from GNSS Data'.

easting = '356662'
northing = '946418'

# construct text string eg: _TEXT easting,northing 10 90 PA10
script_str_txt = f'_TEXT {easting},{northing} 10 90 {name}\n'

# construct point string eg: _POINT easting,northing
script_str_pt = f'_POINT {easting},{northing}\n'

with open(f"{file_num}_{sch_name}.scr", "a") as f:
    f.write(script_str_txt)

The result of the script:


A more practical application code is this one below. It reads a large CSV file containing school names, then groups the data by the school names and generate .scr file for each group.
sch_dict = {'GSS TOTO WEST TOTO LGC':1, 'GJSS ASOPADA KARU LGC':2, 'GJSS KURMIN TAGWAYE AKWANG':3, 'GJSS LAFIA SOUTH LAFIA LGC':4, 'GJSS SAMBGOBARAU KOKONA LGC':5, 'GOV DAY SEC OBI LGC':6, 'GS LOKO NASARAWA LGC':7, 'GSS ALUSHI KEANA LGC':8, 'GSS AZUB CENTRAL LAFIA LGC':9, 'GSS EFUGBORINGO DOMA LGC':10, 'GSS GUDI AKWANGA LGC':11, 'GSS KEKURA AWE LGC':12, 'GSS MAMA WAMBA LGC':13, 'GSS SABONGARI KEFFI KEFFI LGC':14, 'ISLAMIYYA LGEA KEANA LGC':15, 'LGEA DHIZILA OBI LGC':16, 'LGEA EFURIGBORINGO DOMA LGC':17, 'LGEA EZZEN MADA STATION EGGON':18, 'LGEA GUDIGE TSOHO NASARAWA LGC':19, 'LGEA IBI AWE LGC':20, 'LGEA KIGBUNA LAFIA LGC':21, 'LGEA MADA HILL AKWANGA LGC':22, 'LGEA PILOT CEN UMAISHA TOTO LG':23, 'LGEA PRI ADOKASA KARU LGC':24, 'LGEA PRI KONZA WAMB LGC':25, 'LGEA PRI NASARAWA EAST NAS LGC':26, 'LGEA REFUGE CAMP LAFIA LGC':27, 'LGEA SAKWATO KOKONA LGC':28, 'LGEA TRANSFER UMAISHA TOT LGC':29, 'NADP PROJECT LAFIA LGC':30, 'RCM GSS NAS EGGON LGC':31}


# Read the GNSS created spread sheet....
df = pd.read_csv(r"sch-24.csv")

group_df = df.groupby('School Name')
group_keys = list(group_df.groups.keys())

for s in group_keys:
    temp_df = group_df.get_group(s)

    for idx, row in temp_df.iterrows():
        easting = row['X']
        northing = row['Y']
        name = row['Name']
        sch_name = row['School Name']
        file_num = sch_dict[sch_name]
    
        # construct text string eg: _TEXT easting,northing 10 90 PA10
        script_str_txt = f'_TEXT {easting},{northing} 10 90 {name}\n'
        # construct point string eg: _POINT easting,northing
        script_str_pt = f'_POINT {easting},{northing}\n'

        with open(f"{file_num}_{sch_name}.scr", "a") as f:
            f.write(script_str_txt)

        with open(f"{file_num}_{sch_name}.scr", "a") as f:
            f.write(script_str_pt)
        
    # break

print('Done...')



Happy coding!