Geospatial Queries
ZiWound's interactive map is powered by MongoDB geospatial queries over the report model's GeoJSON point field. This page is a focused look at the geo side โ it builds directly on the Report model and the Search & Indexes pages.
The data modelโ
A report carries a GeoJSON Point โ longitude/latitude, in that order:
// report.ts
point: geoJSONStruct("Point"), // { type: "Point", coordinates: [lng, lat] }
geoJSONStruct comes from the mongodb driver's schema types (re-exported through npmDeps). The index must be 2dsphere for GeoJSON queries:
await db.collection("report").createIndex({ point: "2dsphere" });
The act โ getRelatedByGeoโ
Source: back/src/report/getRelatedByGeo/.
export const getRelatedByGeoFn: ActFn = async (body) => {
const { set } = body.details;
const { center, radiusKm } = set;
// $centerSphere takes radius in RADIANS: kilometers / earth's radius
const filter = {
point: {
$geoWithin: { $centerSphere: [center, radiusKm / 6371] },
},
};
if (set.status) filter.report_status = set.status;
if (set.searchTerm) filter.$text = { $search: set.searchTerm };
return await reports.find({ filter, projection: get, limit, skip });
};
Validator shape:
export const getRelatedByGeoValidator = () =>
object({
set: object({
center: array(number()), // [lng, lat]
radiusKm: number(),
status: optional(enums(report_status_array)),
searchTerm: optional(string()),
limit: optional(number()),
skip: optional(number()),
}),
get: coreApp.schemas.selectStruct("report", { ... }),
});
How the frontend drives itโ
The map (MapLibre GL + Leaflet) listens for a viewport change, computes the visible center + radius, and calls getRelatedByGeo. Because the act returns approved reports by default, the frontend passes status: "Approved" to keep pending reports off the public map.
// front/src/app/[locale]/map/... (simplified)
const { docs } = await callLesan("getRelatedByGeo", {
set: { center: mapCenter, radiusKm: visibleRadius, status: "Approved" },
get: { title: true, point: true, report_status: true },
});
Key takeawaysโ
geoJSONStruct("Point")+2dsphereindex is all Lesan needs for geospatial โ there's no Lesan-specific geo API, just the MongoDB field type and index.- Radians math:
$centerSphereradius is in radians; the act converts km (km / 6371). - Composable filters โ geospatial, text, and status filters combine in one
filterobject, so map search and text search work together. - Reuse the same projection โ the act's
getvalidator reusesselectStruct("report", ...), so the client controls exactly what each map marker carries.
Next: File Uploads.