Project Overview
GIS Server — Photogrammetry & Spatial Data Delivery Platform
| CPU | Intel i7-8750H · 6C/12T · 2.2GHz |
| RAM | 32 GB DDR4 |
| GPU | RTX 2060 Mobile · 6GB VRAM · CUDA 13.2 |
| Storage | 238GB NVMe · 931GB HDD (future SSD) |
| OS | Linux Mint 22.3 (Ubuntu Noble) |
| Network | Gigabit Ethernet · 192.168.14.133 |
| Driver | NVIDIA 595.71.05 |
Installation Progress
7-phase GIS server setup · Started June 2026
/opt/gis-data/ with incoming, webodm, geoserver, www subdirectories. TrueNAS SMB mount point reserved.sosurvey-gis active with 4 connections (KUL + SIN). DNS records created for maps, webodm, geoserver subdomains.@sosurvey.com.my and @d-alammaya.link allowed.dev.sosurvey.com.my. Company microsite for sosurvey.com.my pending.Tasks
Server setup · First project workflow · Future improvements
Project Journal
Chronological log of decisions, discoveries, and milestones
Key discoveries: Secure Boot was blocking NVIDIA driver kernel module loading (alx driver PCIe AER errors also caused Ethernet DHCP failures). Both resolved — Secure Boot disabled, Ethernet now stable on Gigabit NIC at 192.168.14.133.
Stack deployed: WebODM (GPU-accelerated, 5 containers) + GeoServer 2.25.0 + Nginx serving Map Viewer. Cloudflare Tunnel with Zero Trust Access policies live for all three subdomains. QGIS 3.34 installed for DWG → Shapefile conversion.
maps.sosurvey.com.my/{client-slug}/ behind Cloudflare Zero Trust email OTP.
Customer data isolation enforced at two levels: GeoServer layer groups (per client) and Cloudflare Access policy (per client email). Clients cannot see each other's survey data.
Infrastructure Diagram
GIS Server stack · sosurvey.com.my · June 2026
sosurvey.com.my"] ZT["Zero Trust
Access Policies"] PAGES["Pages
dev.sosurvey.com.my"] TUNNEL["Tunnel
sosurvey-gis
KUL + SIN"] end subgraph CLIENT["👤 Users"] STAFF["Staff
@sosurvey.com.my
@d-alammaya.link"] CUSTOMER["Customer
Email OTP"] end subgraph SERVER["🖥️ GIS Server — 192.168.14.133"] CLOUDFLARED["cloudflared
daemon"] subgraph DOCKER["Docker Containers"] WEBODM["WebODM
:8000"] GEOSERVER["GeoServer
:8600"] NGINX["Nginx
:8080"] NODE["NodeODM
GPU Node"] REDIS["Redis"] DB["Postgres"] end QGIS["QGIS Desktop
DWG → Shapefile"] GPU["RTX 2060
CUDA 13.2"] STORAGE["/opt/gis-data"] end STAFF -->|"Email OTP"| ZT CUSTOMER -->|"Email OTP"| ZT ZT --> TUNNEL TUNNEL --> CLOUDFLARED CLOUDFLARED --> WEBODM CLOUDFLARED --> GEOSERVER CLOUDFLARED --> NGINX WEBODM --> NODE NODE --> GPU WEBODM --> DB WEBODM --> REDIS NGINX -->|"WMS proxy"| GEOSERVER QGIS -->|"Shapefile"| STORAGE STORAGE --> GEOSERVER STORAGE --> WEBODM style CF fill:#f0f4ff,stroke:#2563eb style SERVER fill:#f0fdf4,stroke:#16a34a style DOCKER fill:#f8fafc,stroke:#94a3b8 style CLIENT fill:#fffbeb,stroke:#d97706
Wiki & Documentation
Workflow guides, runbooks, and reference docs
End-to-End Survey Workflow
1. Field Data Collection
- Conduct drone flight, capture raw JPEG/TIF images + GCP coordinates
- Total station / GNSS survey for utilities, boundaries, polygons
- Draw final CAD layers in ZWCAD on office PC → export DWG
2. Import to Server
Copy via USB to the server:
# Drone images
/opt/gis-data/incoming/drone-images/{project-name}/
# ZWCAD DWG export
/opt/gis-data/incoming/dwg/{client-slug}/
3. Process Drone Imagery
- Login to webodm.sosurvey.com.my (email OTP)
- Create new project → Upload images from
/opt/gis-data/incoming/drone-images/{project}/ - Select processing preset → High Resolution with GPU enabled
- Download output: orthophoto
.tif, point cloud.las - Save orthophoto to
/opt/gis-data/geoserver/data/{client-slug}/
4. Convert CAD to GeoServer
- Open QGIS on server desktop
- Open DWG from
/opt/gis-data/incoming/dwg/{client-slug}/ - Set correct CRS (GDM2000 / Peninsula RSO or WGS84)
- Export layers as Shapefile →
/opt/gis-data/geoserver/data/{client-slug}/
5. Publish to GeoServer
- Access GeoServer at
localhost:8600/geoserver(from server desktop) - Create workspace →
{client-slug} - Add stores → orthophoto GeoTIFF + Shapefile
- Publish layers → create layer group
{client-slug}_all
6. Deliver to Client
- Create
/opt/gis-data/www/{client-slug}/index.html(Leaflet viewer) - Add client email to Zero Trust policy for
maps.sosurvey.com.my - Send client link: maps.sosurvey.com.my/{client-slug}/
- Client receives email OTP on first access
WebODM Processing Guide
Access
Navigate to webodm.sosurvey.com.my — authenticate with your @sosurvey.com.my or @d-alammaya.link email via OTP.
Recommended Settings
| Setting | Value | Notes |
|---|---|---|
| Processing Node | node-odx-1 (GPU) | Always use GPU node |
| Preset | High Resolution | For survey-grade output |
| Feature Quality | High | Balance speed vs quality |
| PC Quality | High | Point cloud density |
| GCP File | Upload if available | Improves georeferencing |
Output Files
odm_orthophoto/odm_orthophoto.tif— GeoTIFF for GeoServerodm_pointcloud/cloud.copc.laz— Point cloudodm_dem/dsm.tif— Digital Surface Model
/opt/gis-data/webodm/projects/. Archive completed projects to free NVMe space before SSD upgrade.QGIS — DWG to GeoServer
Open DWG File
QGIS → Layer → Add Layer → Add Vector Layer
Format: DXF/DWG
File: /opt/gis-data/incoming/dwg/{client-slug}/survey.dwg
Set CRS
Right-click layer → Set CRS. For Peninsular Malaysia:
- EPSG:4742 — GDM2000 (recommended for new surveys)
- EPSG:3375 — GDM2000 / Peninsula RSO (projected)
- EPSG:4326 — WGS84 (if GPS coordinates)
Export to Shapefile
Right-click layer → Export → Save Features As
Format: ESRI Shapefile (or GeoPackage)
CRS: EPSG:4326 (reproject to WGS84 for web use)
Output: /opt/gis-data/geoserver/data/{client-slug}/{layername}.shp
Onboard a New Client
Checklist
- ☐ Create GeoServer workspace:
{client-slug} - ☐ Upload orthophoto + Shapefile layers
- ☐ Publish layer group:
{client-slug}_all - ☐ Create map viewer:
/opt/gis-data/www/{client-slug}/index.html - ☐ Add client email to Cloudflare Zero Trust → Maps policy
- ☐ Test login as client (use incognito browser)
- ☐ Send client their URL:
maps.sosurvey.com.my/{client-slug}/
Add Email to Zero Trust
Cloudflare Dashboard → Zero Trust → Access → Applications → SO Survey Maps → Edit → Policies → Staff Access → Add email rule.
Build a Client Map Page
Create /opt/gis-data/www/{client-slug}/index.html using this template:
<!DOCTYPE html>
<html><head>
<title>{Client Name} — Survey Map</title>
<link rel="stylesheet"
href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<style>#map{height:100vh;width:100%}</style>
</head><body>
<div id="map"></div>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script>
const map = L.map('map').setView([3.1390, 101.6869], 16);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{attribution:'© OpenStreetMap'}).addTo(map);
L.tileLayer.wms('https://geoserver.sosurvey.com.my/geoserver/wms',{
layers: '{client-slug}_all',
format: 'image/png',
transparent: true,
version: '1.1.0',
headers: {
'CF-Access-Client-Id': '{service-token-id}',
'CF-Access-Client-Secret': '{service-token-secret}'
}
}).addTo(map);
</script>
</body></html>
Replace {client-slug} with the GeoServer workspace name. Service token credentials are in /opt/gis-data/.geoserver-token.
Manage Docker Services
Check Status
docker ps --format 'table {{.Names}}\t{{.Status}}'
WebODM
# Start/Stop/Restart
cd /opt/gis-data/webodm-app
./webodm.sh start --gpu
./webodm.sh stop
./webodm.sh restart
# View logs
docker logs webapp --tail 50 -f
GeoServer + Nginx
cd /opt/gis-data
docker compose up -d # start
docker compose down # stop
docker compose restart # restart
docker compose logs -f # logs
Cloudflare Tunnel
sudo systemctl status cloudflared
sudo systemctl restart cloudflared
sudo journalctl -u cloudflared -f
SSD Upgrade Guide
Steps
# 1. Stop all services
cd /opt/gis-data && docker compose down
cd /opt/gis-data/webodm-app && ./webodm.sh stop
# 2. Physically swap HDD → SSD
# 3. Format new SSD
sudo mkfs.ext4 /dev/sda1
# 4. Mount temporarily and copy data
sudo mount /dev/sda1 /mnt/newssd
sudo cp -a /opt/gis-data/. /mnt/newssd/
sudo umount /mnt/newssd
# 5. Update fstab for permanent mount
sudo blkid /dev/sda1 # get UUID
echo "UUID=xxx /opt/gis-data ext4 defaults 0 2" | sudo tee -a /etc/fstab
# 6. Remount and verify
sudo mount -a
ls /opt/gis-data
# 7. Restart services
cd /opt/gis-data && docker compose up -d
cd /opt/gis-data/webodm-app && ./webodm.sh start --gpu
TrueNAS SMB Setup
Mount TrueNAS Share
# Install CIFS utilities
sudo apt install -y cifs-utils
# Create credentials file
sudo tee /etc/smb-creds <<EOF
username=survey-sync
password=YOUR_TRUENAS_PASSWORD
domain=WORKGROUP
EOF
sudo chmod 600 /etc/smb-creds
# Add to /etc/fstab
//TRUENAS-IP/survey-sync /opt/gis-data/incoming \
cifs credentials=/etc/smb-creds,uid=sodc,gid=sodc,\
_netdev,iocharset=utf8 0 0
# Mount
sudo mount -a
Auto-Sync from Field Laptop
On the field laptop (Windows), map the TrueNAS share as a network drive and copy drone images there. The server's /opt/gis-data/incoming/drone-images/ folder will update automatically.