๐ง๏ธ Cropped precision Each polygon is pre-clipped to the exact circular area you queried โ no post-processing needed on your side.
โก Binary-efficient Protobuf encoding cuts payload size significantly compared to JSON, making it ideal for mobile and embedded clients.
๐บ๏ธ GeoJSON-ready Two decode steps are all it takes to go from raw bytes to shapes you can drop straight into geojson.io or Mapbox.
What the endpoint returns
POST /api/precipitations/geometry responds with a raw binary body (Content-Type: application/x-protobuf).
The bytes encode a PrecipitationFeatureCollection message โ a list of PrecipitationFeature items, each carrying a cropped MultiPolygon geometry and typed precipitation properties.
Do not try to parse the response body as JSON or text. It is a protobuf binary stream and will look like garbage if treated as a string.
Proto schema ๐
Below are the proto message definitions used by iklim.co for this endpoint.
// Shared geometry primitives
message Coordinate {
double lng = 1 ;
double lat = 2 ;
}
message Ring {
repeated Coordinate coordinates = 1 ;
}
message PolygonShape {
repeated Ring rings = 1 ;
}
message MultiPolygonShape {
repeated PolygonShape polygons = 1 ;
}
message Geometry {
enum GeometryType {
POINT = 0 ;
LINE_STRING = 1 ;
POLYGON = 2 ;
MULTI_POINT = 3 ;
MULTI_LINE_STRING = 4 ;
MULTI_POLYGON = 5 ;
GEOMETRY_COLLECTION = 6 ;
}
GeometryType type = 1 ;
oneof shape {
Coordinate point = 2 ;
LineStringShape line_string = 3 ;
PolygonShape polygon = 4 ;
MultiPointShape multi_point = 5 ;
MultiLineStringShape multi_line_string = 6 ;
MultiPolygonShape multi_polygon = 7 ;
GeometryCollectionShape geometry_collection = 8 ;
}
}
// Precipitation-specific messages
message PrecipitationProperties {
string intensity = 1 ; // e.g. "DRIZZLE", "LIGHT", "MODERATE"
int64 radar_timestamp = 2 ; // epoch milliseconds
bool forecast = 3 ; // true = NWP (Numerical Weather Prediction) forecast, false = radar observation
}
message PrecipitationFeature {
string id = 1 ;
Geometry geometry = 2 ;
PrecipitationProperties properties = 3 ;
}
message PrecipitationFeatureCollection {
repeated PrecipitationFeature features = 1 ;
}
Copy the full geojson.proto file into your project before running the decoder steps below. The protobufjs loader needs the file at runtime.
Decoding & converting to GeoJSON ๐ ๏ธ
Why two steps? GeoJSON is a JSON text format (RFC 7946) โ it has no official protobuf schema. Its coordinates are bare nested arrays ([[lng, lat], ...]), which protobuf cannot represent without a named wrapper message. iklim.coโs binary schema uses typed messages (Coordinate, Ring, PolygonShape, โฆ) optimised for compact encoding, so a structural reshaping step is always required after decoding.
Step-by-step guide ๐ช
All-in-one snippet โก
GeoJSON output ๐
Install protobufjs ๐ฆ
npm install protobufjs
# or
yarn add protobufjs
Fetch the binary response ๐
Tell fetch to read the response as an ArrayBuffer, then wrap it in a Uint8Array for the proto decoder. const response = await fetch ( "/api/precipitations/geometry" , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
"Authorization" : "Bearer <your-token>"
},
body: JSON . stringify ({
eventIds: [ "69baa6b76e0fe76a1957273f" , "4a1c3e9d8f2b07e5c6d4a1b2" ],
center: { lat: 36.80 , lng: 32.33 },
radius: 25000
})
});
const buffer = await response . arrayBuffer ();
const bytes = new Uint8Array ( buffer );
Decode the protobuf bytes ๐
Load geojson.proto and decode the bytes into a JavaScript object. import protobuf from "protobufjs" ;
const root = await protobuf . load ( "geojson.proto" );
const PrecipitationFeatureCollection = root . lookupType (
"PrecipitationFeatureCollection"
);
const fc = PrecipitationFeatureCollection . decode ( bytes );
At this point fc looks like: {
"features" : [{
"id" : "69baa6b76e0fe76a1957273f" ,
"geometry" : {
"type" : "MULTI_POLYGON" ,
"multiPolygon" : {
"polygons" : [{
"rings" : [{
"coordinates" : [
{ "lng" : 32.33 , "lat" : 36.80 },
{ "lng" : 32.34 , "lat" : 36.81 }
]
}]
}]
}
},
"properties" : {
"intensity" : "DRIZZLE" ,
"radarTimestamp" : 1773846900000 ,
"forecast" : true
}
}]
}
Convert to standard GeoJSON ๐บ๏ธ
Transform the decoded object into RFC 7946-compliant GeoJSON. function toGeoJson ( fc ) {
return {
type: "FeatureCollection" ,
features: fc . features . map ( feature => ({
type: "Feature" ,
id: feature . id ,
geometry: {
type: "MultiPolygon" ,
coordinates: feature . geometry . multiPolygon . polygons . map ( polygon =>
polygon . rings . map ( ring =>
ring . coordinates . map ( coord => [ coord . lng , coord . lat ])
)
)
},
properties: {
intensity: feature . properties . intensity ,
radarTimestamp: feature . properties . radarTimestamp ,
forecast: feature . properties . forecast
}
}))
};
}
const geoJson = toGeoJson ( fc );
console . log ( JSON . stringify ( geoJson , null , 2 ));
Drop this single async function into your project โ it handles fetch, decode, and GeoJSON conversion end-to-end. import protobuf from "protobufjs" ;
async function fetchPrecipitationGeoJson ({ eventIds , center , radius , token }) {
// 1 โ Fetch binary response
const response = await fetch ( "/api/precipitations/geometry" , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
"Authorization" : `Bearer ${ token } `
},
body: JSON . stringify ({ eventIds , center , radius })
});
if ( ! response . ok ) throw new Error ( `HTTP ${ response . status } ` );
const bytes = new Uint8Array ( await response . arrayBuffer ());
// 2 โ Decode protobuf
const root = await protobuf . load ( "geojson.proto" );
const PrecipitationFeatureCollection = root . lookupType (
"PrecipitationFeatureCollection"
);
const fc = PrecipitationFeatureCollection . decode ( bytes );
// 3 โ Convert to GeoJSON
return {
type: "FeatureCollection" ,
features: fc . features . map ( f => ({
type: "Feature" ,
id: f . id ,
geometry: {
type: "MultiPolygon" ,
coordinates: f . geometry . multiPolygon . polygons . map ( poly =>
poly . rings . map ( ring =>
ring . coordinates . map ( c => [ c . lng , c . lat ])
)
)
},
properties: {
intensity: f . properties . intensity ,
radarTimestamp: f . properties . radarTimestamp ,
forecast: f . properties . forecast
}
}))
};
}
// Usage
const geoJson = await fetchPrecipitationGeoJson ({
eventIds: [ "69baa6b76e0fe76a1957273f" , "4a1c3e9d8f2b07e5c6d4a1b2" ],
center: { lat: 36.80 , lng: 32.33 },
radius: 25000 ,
token: "<your-token>"
});
The final GeoJSON object is RFC 7946-compliant and renders correctly in any GeoJSON viewer. {
"type" : "FeatureCollection" ,
"features" : [
{
"type" : "Feature" ,
"id" : "69baa6b76e0fe76a1957273f" ,
"geometry" : {
"type" : "MultiPolygon" ,
"coordinates" : [
[
[
[ 32.33 , 36.80 ],
[ 32.34 , 36.81 ],
[ 32.35 , 36.80 ],
[ 32.33 , 36.80 ]
]
]
]
},
"properties" : {
"intensity" : "DRIZZLE" ,
"radarTimestamp" : 1773846900000 ,
"forecast" : true
}
}
]
}
๐งช Copy the output above and paste it into geojson.io to visually verify your geometry is correct before integrating with your map renderer.
Field reference ๐
PrecipitationFeature
Field Type Description idstring Unique identifier of the radar geometry record. geometryobject Cropped MultiPolygon geometry (see below). propertiesobject Typed precipitation metadata.
geometry (MultiPolygon)
Field Path Description typegeometry.typeAlways MULTI_POLYGON in the proto object; becomes "MultiPolygon" in GeoJSON. polygonsgeometry.multiPolygon.polygons[]Array of polygons. Each polygon has one or more rings. ringspolygons[].rings[]First ring is the exterior boundary; additional rings are holes. coordinatesrings[].coordinates[]Array of { lng, lat } objects โ convert to [lng, lat] arrays for GeoJSON.
properties
Field Type Description intensitystring Precipitation intensity label. See the IntensityType catalog below. radarTimestampnumber UTC epoch milliseconds of the radar scan that detected this event. forecastboolean true = NWP (Numerical Weather Prediction) model forecast polygon; false = live radar observation.
IntensityType catalog
Value Display name Map color DRIZZLEDrizzle #7ae1e8LIGHTLight #00c8d8MODERATEModerate #2d7beaHEAVYHeavy #be46ebVERY_HEAVYVery Heavy #e36546EXTREMEExtreme #ecd940