Пређи на главни садржај

Geospatial Map Stack

Map-centric item discovery is central to Borrowd. To deliver smooth vector map rendering without incurring commercial mapping API fees or running heavy tile-server infrastructure, I built an end-to-end open-source spatial stack using PMTiles, MapLibre GL JS, and PostGIS.

The architecture strictly separates the static base map rendering from the dynamic item querying.

Self-Hosted Vector Base Maps (PMTiles)​

Traditional vector map stacks require running dedicated tile-server containers (like TileServer-GL or Tegola) that continuously query database backends or unpack tile archives on the fly.

To eliminate active tile-server compute, I adopted PMTiles—a single-file archive format for tile pyramids. I serve the global base map as a single .pmtiles file directly out of MinIO object storage.

MapLibre GL JS reads PMTiles archives directly from MinIO using standard HTTP Range Requests. The browser fetches only the exact bytes needed for the active map zoom level and viewport bounding box, making base map rendering incredibly fast and practically free to host.

Dynamic Spatial Queries (PostGIS)​

While the base map is static, item listings are dynamic. Searching for items within a specific distance or visible map viewport requires high-performance spatial querying. I built a location indexing engine using PostgreSQL + PostGIS.

As users drag or zoom the map, the frontend sends the visible map coordinates. The backend queries PostgreSQL and returns dynamic GeoJSON overlay layers directly to the MapLibre canvas.

Spatial Query Patterns​

  • Viewport Bounding Box Queries: The frontend sends visible map coordinates (MinLon, MinLat, MaxLon, MaxLat). PostGIS evaluates ST_Within(location, ST_MakeEnvelope(...)) using spatial R-Tree indexes (GIST).
  • Radius Search Queries: For distance-filtered searches (e.g., "drill presses within 5 km"), PostGIS uses ST_DistanceSphere to calculate precise spherical distances between items and the user's location.
Non-Blocking Spatial Queries

I wrote custom PointType and PostgisCodecRegistrar integrations for Komapper and R2DBC. This allows the backend to execute complex PostGIS spatial queries completely asynchronously, without blocking application threads or paying heavy ORM overhead.