German Federal Agency for Technical Relief (THW)
- 88,000 volunteers (helpers)
- 668 local sections
www.thw.de
Technical Platoon
~30 helpers
Command squad
Rescue squad
Technical squad
www.thw-osnabrueck.de
Command squad
Supports the platoon leader with:
- Radio communication
- Situation map
- Personnel overview
Digital situation map requirements
- Provides full coverage of Germany
- Works offline and off-grid
- Allows drawing directly on the map
- Includes a search option for places and streets
- Accessible via a WiFi hotspot
- Supports UTMREF coordinates
MBTiles
The MBTiles format is based on SQLite
Planetiler generates germany.mbtiles (2.8 GB) from the raw OpenStreetMap data.
Data is provided via FastAPI (region='germany'):
GET /api/vector/tiles/{region}/{zoom_level}/{x}/{y}.pbf
Raspberry Pi Zero 2 W
- Low-cost and energy-efficient
- 64-bit quad-core CPU
- Can be powered by:
- A power supply
- A power bank
- A phone charger in the vehicle
WiFi access point
Created using AccessPopup from www.raspberryconnect.com
- Connects to a known WiFi network if available
- Creates a WiFi hotspot if no network is available
Display the map
Map styles
Modified styles from OpenMapTiles to consider local paths of tiles, fonts and sprites.
OSM Basic
OSM Liberty
Administrative boundaries
BKG provides administrative boundaries in VG5000
GeoPandas converts shape files to GeoJSON
Postcode regions
Simplified GeoJSON and CSV file downloaded from www.suche-postleitzahl.org
Postcode search
Created a PyO3 module in Rust to perform fast search operations on GeoJSON files
from point_in_geojson import PointInGeoJSON
with open("post_codes_germany.json") as f:
pig = PointInGeoJSON(f.read())
def get_plz_from_lat_lon(lat: float, lon: float) -> str | None:
result = pig.point_included_with_properties(lon, lat)
return result[0]["plz"] if len(result) == 1 else None
def get_features_for_plz(plz: str) -> list[dict]:
return pig.features_with_property_str(
"plz", plz, "starts_with")
https://github.com/jaluebbe/point_in_geojson
Extraction of places
Extract nodes from OSM PBF using PyOsmium
| Tag |
Value |
| name |
Weststadt |
| place |
suburb |
- name tag must exist
- place tag must be one of:
- city
- village
- suburb
- quarter
- neighbourhood
Data stored as SQLite for further processing
Extraction of streets
Extract ways with their nodes from OSM PBF
| Tag |
Value |
| name |
Ebereschenweg |
| highway |
residential |
- name tag must exist
- highway tag must be one of:
- primary
- tertiary
- residential
- pedestrian
- ...
Data stored as SQLite for further processing
Creation of the search database
Cluster places and streets by post codes and administrative regions for a more efficient search.
Most common search results in Germany:
| Type |
Name |
Occurrences |
| Place |
Altstadt |
79 |
| Street |
Hauptstraße |
6,435 |
Simplify streets to a single point.
Line string simplification
def simplify_locations(locations):
multipoint = MultiPoint(locations)
centroid = multipoint.centroid
return min(
locations,
key=lambda point: Point(point).distance(centroid)
)
Using Shapely
each street is reduced from multiple lines to a single point on the street.
Military Grid Reference System (MGRS/UTMREF)
| Conversion |
| Lat., Lon. |
51.799478, 8.07679 |
| UTM |
32 N 436339 5739139 |
| MGRS/UTMREF |
32U MC 36338 39139 |
| Coordinate |
Precision |
| 32U MC |
100 km |
| 32U MC 3 6 |
10 km |
| 32U MC 36 39 |
1 km |
| 32U MC 363 391 |
100 m |
| 32U MC 3633 3913 |
10 m |
| 32U MC 36339 39139 |
1 m |
PyGeodesy for lat/lon conversion needs zero padding.
Live demo
Long Range Wide Area Network (LoRaWAN)
- Operates at 868 MHz in Europe
- Long range, low power, low data rate
- Battery powered sensor nodes
- Temperature
- Water level
- GPS location
- Required infrastructure
LORAWAN experiments
Gateway with server
Temperature sensor node
LORAWAN range test