{"id":48630799,"url":"https://github.com/dmarkham/grib2","last_synced_at":"2026-04-09T05:02:53.186Z","repository":{"id":350129009,"uuid":"1205385324","full_name":"dmarkham/grib2","owner":"dmarkham","description":"Pure Go GRIB2 reader/writer — all major compression formats, no CGo, no external dependencies","archived":false,"fork":false,"pushed_at":"2026-04-09T01:32:03.000Z","size":164,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-09T03:14:33.658Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmarkham.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-08T23:14:47.000Z","updated_at":"2026-04-09T01:32:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dmarkham/grib2","commit_stats":null,"previous_names":["dmarkham/grib2"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/dmarkham/grib2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmarkham%2Fgrib2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmarkham%2Fgrib2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmarkham%2Fgrib2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmarkham%2Fgrib2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmarkham","download_url":"https://codeload.github.com/dmarkham/grib2/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmarkham%2Fgrib2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31586412,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"online","status_checked_at":"2026-04-09T02:00:06.848Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-09T05:02:47.208Z","updated_at":"2026-04-09T05:02:53.174Z","avatar_url":"https://github.com/dmarkham.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# grib2\n\nA pure Go library for reading and writing GRIB2 (WMO FM 92 GRIB Edition 2) files.\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/dmarkham/grib2.svg)](https://pkg.go.dev/github.com/dmarkham/grib2)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n## Overview\n\nGRIB2 is the World Meteorological Organization's binary format for gridded meteorological data, used by weather services worldwide (NOAA, ECMWF, JMA, DWD, etc.). This library provides a complete, idiomatic Go implementation for both decoding and encoding GRIB2 messages -- no CGo, no external C libraries, no system dependencies.\n\n## Features\n\n### Packing Formats\n\n| Format | Template | Decode | Encode |\n|--------|----------|--------|--------|\n| Simple packing | 5.0 | Yes | Yes |\n| Complex packing | 5.2 | Yes | Yes |\n| Complex packing + spatial differencing | 5.3 | Yes | Yes |\n| IEEE floating point (32/64-bit) | 5.4 | Yes | Yes |\n| JPEG2000 | 5.40 | Yes | Yes |\n| PNG | 5.41 | Yes | Yes |\n| CCSDS/AEC (Adaptive Entropy Coding) | 5.42 | Yes | Yes |\n| Run-length packing with level values | 5.200 | Yes | -- |\n\n### Grid Definition Templates (Section 3)\n\n| Grid Type | Template |\n|-----------|----------|\n| Latitude/Longitude (equidistant cylindrical) | 3.0 |\n| Rotated Latitude/Longitude | 3.1 |\n| Mercator | 3.10 |\n| Polar Stereographic | 3.20 |\n| Lambert Conformal | 3.30 |\n| Gaussian Latitude/Longitude (regular and reduced) | 3.40 |\n| Space View Perspective (geostationary satellite) | 3.90 |\n\n### Product Definition Templates (Section 4)\n\n| Product Type | Template |\n|--------------|----------|\n| Analysis or forecast at a point in time | 4.0 |\n| Individual ensemble forecast | 4.1 |\n| Derived ensemble forecast | 4.2 |\n| Statistically processed values over time interval | 4.8 |\n| Ensemble forecast over time interval | 4.11 |\n| Derived ensemble over time interval | 4.12 |\n\n### High-Level API\n\n- **Bitmap support** -- automatic expansion of bitmap-masked fields (NaN for missing points)\n- **Scanning mode reordering** -- all 16 scanning mode combinations normalized to canonical west-to-east, north-to-south order\n- **Grid coordinates** -- `GridCoordinates()` returns lat/lon arrays for all supported grid types, including projected grids (Lambert, Polar Stereographic, Mercator, Space View)\n- **Nearest-neighbour lookup** -- `NearestValue(lat, lon)` with O(1) direct index computation for regular grids, haversine fallback for others\n- **Bilinear interpolation** -- `BilinearValue(lat, lon)` for smooth value estimation between grid points\n- **Code table lookups** -- human-readable names for disciplines, centres, parameters, surface types, and all template numbers\n- **Multi-message files** -- `Decoder` reads files containing any number of concatenated GRIB2 messages\n- **Byte-perfect round-trips** -- `ReadMessage` followed by `WriteMessage` produces identical bytes\n\n## Installation\n\n```\ngo get github.com/dmarkham/grib2\n```\n\nRequires Go 1.25 or later. Zero external dependencies outside the standard library.\n\n## Quick Start\n\n### Read a GRIB2 file and extract values\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/dmarkham/grib2\"\n)\n\nfunc main() {\n\tf, err := os.Open(\"forecast.grib2\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer f.Close()\n\n\tdec := grib2.NewDecoder(f)\n\tfor {\n\t\tmsg, err := dec.Decode()\n\t\tif err != nil {\n\t\t\tbreak // io.EOF or parse error\n\t\t}\n\n\t\tfmt.Printf(\"Discipline: %s\\n\", grib2.DisciplineName(msg.Section0.Discipline))\n\t\tfmt.Printf(\"Centre:     %s\\n\", grib2.CentreName(msg.Section1.Centre))\n\t\tfmt.Printf(\"Reference:  %s\\n\", msg.Section1.ReferenceTime())\n\n\t\tfor i, field := range msg.Fields {\n\t\t\tvalues, err := field.Values()\n\t\t\tif err != nil {\n\t\t\t\tfmt.Printf(\"  field %d: unpack error: %v\\n\", i, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfmt.Printf(\"  field %d: %d values, grid template %d, packing template %d\\n\",\n\t\t\t\ti, len(values), field.Section3.TemplateNumber, field.Section5.TemplateNumber)\n\t\t}\n\t}\n}\n```\n\n### Query a specific location (nearest-neighbour)\n\n```go\nfield := msg.Fields[0]\n\n// Find the grid point closest to Denver, CO\nvalue, idx, lat, lon, err := field.NearestValue(39.7392, -104.9903)\nif err != nil {\n\tpanic(err)\n}\nfmt.Printf(\"Nearest grid point: (%.4f, %.4f) index=%d value=%.2f\\n\", lat, lon, idx, value)\n\n// Or use bilinear interpolation for smoother results\ninterpolated, err := field.BilinearValue(39.7392, -104.9903)\nif err != nil {\n\tpanic(err)\n}\nfmt.Printf(\"Interpolated value: %.2f\\n\", interpolated)\n```\n\n### Write a GRIB2 file\n\n```go\npackage main\n\nimport (\n\t\"os\"\n\n\t\"github.com/dmarkham/grib2\"\n)\n\nfunc main() {\n\t// Create a simple 4x3 temperature grid\n\tvalues := []float64{\n\t\t280.0, 281.0, 282.0, 283.0,\n\t\t279.0, 280.5, 281.5, 282.5,\n\t\t278.0, 279.5, 280.5, 281.0,\n\t}\n\n\t// Pack with simple packing at 16 bits per value\n\tpacked, tmpl50, err := grib2.PackSimple(values, 16)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tmsg := \u0026grib2.Message{\n\t\tSection0: grib2.Section0{\n\t\t\tDiscipline: 0, // Meteorological\n\t\t\tEdition:    2,\n\t\t},\n\t\tSection1: grib2.Section1{\n\t\t\tCentre:              7,  // NCEP\n\t\t\tMasterTablesVersion: 2,\n\t\t\tYear:                2025, Month: 1, Day: 15,\n\t\t\tHour: 12, Minute: 0, Second: 0,\n\t\t},\n\t\tFields: []grib2.Field{{\n\t\t\tSection3: grib2.Section3{\n\t\t\t\tNumberOfDataPoints: 12,\n\t\t\t\tTemplateNumber:     0,\n\t\t\t\tTemplate: grib2.Template30{\n\t\t\t\t\tNi: 4, Nj: 3,\n\t\t\t\t\tLatitudeOfFirstGridPoint:  40000000,  // 40.0N\n\t\t\t\t\tLongitudeOfFirstGridPoint: 350000000, // 350.0E (-10.0)\n\t\t\t\t\tLatitudeOfLastGridPoint:   38000000,  // 38.0N\n\t\t\t\t\tLongitudeOfLastGridPoint:  353000000, // 353.0E (-7.0)\n\t\t\t\t\tIDirectionIncrement:       1000000,   // 1.0 degree\n\t\t\t\t\tJDirectionIncrement:       1000000,   // 1.0 degree\n\t\t\t\t},\n\t\t\t},\n\t\t\tSection4: grib2.Section4{\n\t\t\t\tTemplateNumber: 0,\n\t\t\t\tTemplate: grib2.Template40{\n\t\t\t\t\tParameterCategory: 0, // Temperature\n\t\t\t\t\tParameterNumber:   0, // Temperature (K)\n\t\t\t\t},\n\t\t\t},\n\t\t\tSection5: grib2.Section5{\n\t\t\t\tNumberOfValues:  12,\n\t\t\t\tTemplateNumber: 0,\n\t\t\t\tTemplate:       tmpl50,\n\t\t\t},\n\t\t\tSection6: grib2.Section6{Indicator: 255}, // no bitmap\n\t\t\tSection7: grib2.Section7{Data: packed},\n\t\t}},\n\t}\n\n\tout, err := os.Create(\"output.grib2\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer out.Close()\n\n\tif err := grib2.WriteMessage(out, msg); err != nil {\n\t\tpanic(err)\n\t}\n}\n```\n\n## API Reference\n\n### Reading\n\n| Function/Type | Description |\n|---------------|-------------|\n| `NewDecoder(r io.Reader) *Decoder` | Create a decoder for reading multiple messages from a stream |\n| `(*Decoder).Decode() (*Message, error)` | Read the next GRIB2 message (returns `io.EOF` when done) |\n| `ReadMessage(r io.Reader) (*Message, error)` | Convenience function to read a single message |\n\n### Message Structure\n\n```\nMessage\n  Section0    -- Indicator (discipline, edition, total length)\n  Section1    -- Identification (centre, reference time, production status)\n  Section2    -- Local Use (optional, centre-specific data)\n  Fields[]    -- One or more data fields, each containing:\n    Section3  -- Grid Definition (template defining the grid geometry)\n    Section4  -- Product Definition (what the data represents)\n    Section5  -- Data Representation (packing method and parameters)\n    Section6  -- Bitmap (missing data mask)\n    Section7  -- Data (packed values)\n```\n\n### Field Methods\n\n| Method | Description |\n|--------|-------------|\n| `Values() ([]float64, error)` | Unpack, apply bitmap, reorder by scanning mode |\n| `GridCoordinates() (lats, lons []float64, error)` | Lat/lon of every grid point in data order |\n| `NearestValue(lat, lon float64)` | Value at closest grid point (O(1) for regular grids) |\n| `BilinearValue(lat, lon float64)` | Bilinear interpolation from 4 surrounding points |\n\n### Writing\n\n| Function | Description |\n|----------|-------------|\n| `WriteMessage(w io.Writer, msg *Message) error` | Write a complete GRIB2 message (patches total length automatically) |\n| `PackSimple(values, bpv)` | Encode with simple packing (Template 5.0) |\n| `PackComplex(values, bpv)` | Encode with complex packing (Template 5.2) |\n| `PackComplexSpatialDiff(values, bpv, order)` | Encode with complex packing + spatial differencing (Template 5.3) |\n| `PackIEEE(values, precision)` | Encode as IEEE 32-bit or 64-bit floats (Template 5.4) |\n| `PackJPEG2000(values, bpv)` | Encode with JPEG2000 lossless compression (Template 5.40) |\n| `PackPNG(values, bpv, width, height)` | Encode with PNG compression (Template 5.41) |\n| `PackCCSDS(values, bpv, blockSize, rsi)` | Encode with CCSDS/AEC compression (Template 5.42) |\n\n### Code Table Lookups\n\n| Function | Table |\n|----------|-------|\n| `DisciplineName(d uint8)` | Table 0.0 |\n| `CentreName(c uint16)` | Common Code Table C-11 |\n| `ParameterName(discipline, category, number uint8)` | Table 4.2.x.x |\n| `ParameterUnit(discipline, category, number uint8)` | Units for Table 4.2 |\n| `ParameterCategoryName(discipline, category uint8)` | Table 4.1.x |\n| `SurfaceTypeName(t uint8)` | Table 4.5 |\n| `GridDefinitionTemplateName(n uint16)` | Table 3.1 |\n| `ProductDefinitionTemplateName(n uint16)` | Table 4.0 |\n| `DataRepresentationTemplateName(n uint16)` | Table 5.0 |\n| `ReferenceTimeName(t uint8)` | Table 1.2 |\n| `ProductionStatusName(s uint8)` | Table 1.3 |\n| `DataTypeName(t uint8)` | Table 1.4 |\n\n## Compression Format Details\n\n| Format | Template | Decode | Encode | Notes |\n|--------|----------|--------|--------|-------|\n| Simple | 5.0 | Yes | Yes | Fast paths for 8/16/32-bit aligned widths; arbitrary bit widths supported |\n| Complex | 5.2 | Yes | Yes | Full group splitting with eccodes-compatible merge optimization |\n| Complex + Spatial Diff | 5.3 | Yes | Yes | Order 1 and 2 spatial differencing; missing value management |\n| IEEE Float | 5.4 | Yes | Yes | 32-bit and 64-bit precision |\n| JPEG2000 | 5.40 | Yes | Yes | Pure Go J2K codec (internal/j2k); lossless mode |\n| PNG | 5.41 | Yes | Yes | Uses Go standard library `image/png`; 8-bit and 16-bit grayscale |\n| CCSDS/AEC | 5.42 | Yes | Yes | Pure Go AEC codec (internal/aec); CCSDS 121.0-B-3 compliant |\n| Run-length | 5.200 | Yes | -- | Variable-base run-length decoding with level values |\n\nAll decode paths are cross-validated against eccodes reference output at tolerances from 1e-4 to 1e-10 depending on the format.\n\n## Testing\n\nThe test suite contains 138 unit tests and 7 fuzz targets covering:\n\n- **Byte-perfect round-trips**: read a GRIB2 file, write it back, compare bytes\n- **Cross-validation against eccodes**: decoded values compared to eccodes `grib_get_data` output for every supported packing format (simple, complex, complex+spatial diff, JPEG2000, PNG, CCSDS, IEEE, run-length)\n- **Encode/decode round-trips**: pack values, unpack them, verify they match within quantization tolerance\n- **Grid coordinate validation**: lat/lon arrays compared against eccodes for regular lat/lon, Mercator, Gaussian, Lambert, Polar Stereographic, and Space View grids\n- **Section-level parsing**: individual tests for all section readers and writers (Sections 0-8)\n- **Template coverage**: dedicated tests for all grid, product, and data representation templates\n- **Fuzz testing**: 7 fuzz targets covering the full message parser and each individual unpacker (JPEG2000, CCSDS, Complex, Run-length, Simple, PNG)\n- **Hardening**: all allocations are capped (100M elements max), all bit extractions are bounds-checked, section sizes are limited to 256 MiB\n\n## Performance\n\n- **Pure Go, zero CGo** -- no overhead from C interop, straightforward cross-compilation, no system library requirements\n- **Fast paths** for common bit widths (8, 16, 32-bit aligned simple packing)\n- **Streaming decoder** -- reads messages one at a time via `Decoder`, suitable for large multi-message files\n- **O(1) nearest-neighbour** for regular lat/lon and regular Gaussian grids (direct index computation instead of brute-force search)\n- **Fuzz-tested and hardened** -- safe to use on untrusted input; allocation caps and bounds checks prevent resource exhaustion\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmarkham%2Fgrib2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmarkham%2Fgrib2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmarkham%2Fgrib2/lists"}