Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SharpPostal

.NET bindings for libpostal, the international street-address parser and normalizer powered by statistical NLP.

SharpPostal lets you call libpostal's two core capabilities from C#:

  • Expansion — turn "120 E 96th St" into normalized variants like "120 east 96th street" and "120 east 96 street", useful for fuzzy address matching and deduplication.
  • Parsing — break "The Book Club 100-106 Leonard St Shoreditch London EC2A 4RH" into labeled components (house_number, road, city, postcode, etc.).

Quick start

If you've just cloned the repo on a fresh machine:

# 1. Build libpostal for Windows by following the upstream guide:
#    https://github.com/openvenues/libpostal#installation-windows
#    Then copy the .dll (libpostal-1.dll) into:
#    src\runtimes\win-x64\native\ 

# 2. Build and run the demo
dotnet build SharpPostal.sln
dotnet run --project demo

Table of contents

  1. Prerequisites
  2. Install the native library
  3. Install the data files
  4. Add SharpPostal to your project
  5. Usage
  6. Threading and lifecycle
  7. Troubleshooting

1. Prerequisites

  • .NET 10 SDK or later — download here.
  • Windows x64. The project ships native binaries for win-x64. Other platforms work in principle (libpostal builds on Linux/macOS), but you'll need to supply the matching native binaries and add runtimes/<rid>/native/ folders for them.
  • ~3 GB free disk space for the model data, plus another ~2 GB of RAM at runtime once everything is loaded.
  • An archive tool that handles .tar.gz (7-Zip, WinRAR, the built-in tar on Windows 10+).

2. Install the native library

Step 1: build (or obtain) libpostal for Windows

Follow the official libpostal Windows installation guide:

https://github.com/openvenues/libpostal#installation-windows

It walks through installing MSYS2/MinGW, cloning libpostal, and running ./bootstrap.sh && ./configure && make && make install. When that's done you'll have a fully built and installed copy of libpostal on your machine, with the native DLL in a folder. Data files are installed when going through the process!

Step 2: copy the native DLL into this repo

When the upstream install finishes, you'll have a folder containing libpostal-1.dll

Copy libpostal-1.dll into:

src\runtimes\win-x64\native\

After copying, the layout should look something like:

src\runtimes\win-x64\native\
└── libpostal-1.dll

Step 3 (optional): verify dependencies are met

If the build later fails with DllNotFoundException, this command tells you what libpostal-1.dll is trying to load:

dumpbin /dependents src\runtimes\win-x64\native\libpostal-1.dll

Standard Windows DLLs (KERNEL32.dll, msvcrt.dll, ucrtbase.dll, api-ms-win-*, ADVAPI32.dll) are always present on Windows. Anything else listed must live in src\runtimes\win-x64\native\ next to libpostal-1.dll.

4. Add SharpPostal to your project

Project reference (working directly from this repo)

dotnet add <YourProject>.csproj reference path\to\SharpPostal\src\SharpPostal.csproj

The native DLL is copied to the output directory automatically. After building, you should see libpostal-1.dll sitting next to your .exe.

5. Usage

Initialize once at startup

libpostal has three independent data subsystems. Load whichever ones you'll use:

using SharpPostal;

const string DataDir = @"C:\libpostal_data";

// For ExpandAddress without explicit language hints
LibPostal.InitializeFromDirectory(DataDir);
LibPostal.InitializeLanguageClassifierFromDirectory(DataDir);

// For ParseAddress
LibPostal.InitializeParserFromDirectory(DataDir);

Each initialization call takes a few seconds and allocates significant memory. Do this once per process, ideally during app startup. If you call them more than once they're no-ops, so they're safe to call from multiple places defensively.

If you prefer the path baked into the DLL at compile time (typically C:\libpostal on Windows builds), use the parameterless versions instead: Initialize(), InitializeLanguageClassifier(), InitializeParser(). The directory-based versions are generally more portable.

Shut down at exit

LibPostal.Shutdown();

This releases all model memory. Optional but tidy.

Expand an address

string[] variants = LibPostal.ExpandAddress("120 E 96th St New York");

foreach (var v in variants)
    Console.WriteLine(v);

// 120 east 96th street new york
// 120 east 96 street new york
// ...

If you know the language ahead of time, pass it. This skips the language classifier (which means you don't even need to call InitializeLanguageClassifier) and is significantly faster on tight loops:

string[] variants = LibPostal.ExpandAddress(
    "Friedrichstraße 123, 10117 Berlin",
    languages: new[] { "de" });

Parse an address

var components = LibPostal.ParseAddress(
    "The Book Club 100-106 Leonard St Shoreditch London EC2A 4RH United Kingdom");

foreach (var (label, value) in components)
    Console.WriteLine($"{label,-15} {value}");

// house            the book club
// house_number     100-106
// road             leonard st
// suburb           shoreditch
// city             london
// postcode         ec2a 4rh
// country          united kingdom

Optionally pass language and country hints to bias the parser:

var components = LibPostal.ParseAddress(
    "Friedrichstraße 123, 10117 Berlin",
    language: "de",
    country: "DE");

Complete example

using System;
using SharpPostal;

const string DataDir = @"C:\libpostal_data";

Console.WriteLine("Initializing libpostal (this takes a few seconds)...");
LibPostal.InitializeFromDirectory(DataDir);
LibPostal.InitializeLanguageClassifierFromDirectory(DataDir);
LibPostal.InitializeParserFromDirectory(DataDir);

try
{
    var expansions = LibPostal.ExpandAddress("120 E 96th St New York");
    Console.WriteLine($"Got {expansions.Length} variants");

    var parsed = LibPostal.ParseAddress("781 Franklin Ave Crown Hts Brooklyn NY 11216");
    foreach (var (label, value) in parsed)
        Console.WriteLine($"  {label,-15} {value}");
}
finally
{
    LibPostal.Shutdown();
}

API reference

Member Purpose
LibPostal.Initialize() / InitializeFromDirectory(path) Load expansion model data. Required for ExpandAddress.
LibPostal.InitializeLanguageClassifier() / InitializeLanguageClassifierFromDirectory(path) Load language detection model. Required for ExpandAddress unless you pass explicit languages.
LibPostal.InitializeParser() / InitializeParserFromDirectory(path) Load address parser model. Required for ParseAddress.
LibPostal.Shutdown() Release all loaded model data.
LibPostal.ExpandAddress(string address, IReadOnlyList<string>? languages = null) Returns string[] of normalized variants.
LibPostal.ParseAddress(string address, string? language = null, string? country = null) Returns AddressComponent[] of (Label, Value) pairs.
AddressComponent readonly record struct with Label and Value properties.

6. Threading and lifecycle

  • Initialization is not thread-safe. Call Initialize* methods from one thread (typically app startup) before any other thread starts using libpostal. The wrapper protects against double-init with Interlocked flags, but the underlying C library still expects single-threaded setup.
  • ExpandAddress and ParseAddress are safe to call concurrently from many threads once setup is complete. The models are read-only after loading.
  • Model data is process-global, not per-instance. You cannot have two configurations of libpostal in the same process.
  • Memory usage is significant. Plan for ~2 GB resident once everything is loaded. The parser alone is the largest single component.

Project structure

SharpPostal/
├── SharpPostal.sln           Solution file
├── src/
│   ├── SharpPostal.csproj    Binding library project
│   ├── AssemblyInfo.cs       [assembly: DisableRuntimeMarshalling]
│   ├── Native.cs             P/Invoke declarations (internal)
│   ├── LibPostal.cs          High-level public API
│   ├── Utf8.cs               UTF-8 marshalling helpers
│   └── runtimes/win-x64/native/
│       └── libpostal-1.dll   Native library
└── demo/
    ├── SharpPostal.Demo.csproj
    └── Program.cs            Sample console app

License

This binding code is released under the MIT license. The native libpostal library is itself MIT-licensed; see its repo for details. The data files are derived from OpenStreetMap and other open data; refer to libpostal's documentation for attribution requirements when redistributing them.

About

.NET bindings for libpostal, the international street-address parser and normalizer powered by statistical NLP.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages