ExPyshp

Hex.pm version Hex Docs Hex.pm downloads License

Read, write and ZIP shapefiles from Elixir or Phoenix applications using pyshp.

Installation

Requires Elixir 1.20 or later. Add ExPyshp to your dependencies in mix.exs:

{:ex_pyshp, "~> 0.2.0"}

Configure Pythonx in your application's config/config.exs, then run mix deps.get:

import Config

config :pythonx, :uv_init,
  uv_version: "0.13.0",
  pyproject_toml: """
  [project]
  name = "my-app"
  version = "0.1.0"
  requires-python = "==3.13.*"
  dependencies = [
    "pyshp==3.1.6",
    "shapely==2.2.0",
    "pyproj==3.8.0",
  ]
  """

If your application already configures Pythonx, add these Python packages to its existing configuration.

Reading

{:ok, name, rows} = ExPyshp.read("features.shp", "features.dbf", "features.shx")

Each row contains its attributes in "record" and a Geo struct in "geometry".

If a matching .prj file exists, it is used automatically. You can also pass its path:

{:ok, name, rows} =
  ExPyshp.read("features.shp", "features.dbf", "features.shx", "features.prj")

Coordinates from a known projection are converted to WGS84. To keep the original coordinates:

ExPyshp.read("features.shp", "features.dbf", "features.shx", "features.prj",
  reproject: false)

Reading returns {:error, name, message} if it fails.

Writing

rows = [
  %{
    "record" => %{"ID" => "1", "NAME" => "Feature1", "VALUE" => 123.4567},
    "geometry" => %Geo.Point{coordinates: {1.0, 2.0}, srid: 4326}
  }
]

{:ok, files} = ExPyshp.write("output", "features", rows, numeric_precision: 4)

The returned paths are files.shp, files.shx, files.dbf, files.prj and files.cpg. The geometry's SRID supplies the .prj file; you can also set projection_srid: 4326 explicitly.

Field names are limited to 10 ASCII characters. Writing returns {:error, message} if it fails, and existing files are not overwritten.

ZIP files

Create a ZIP from the generated files:

paths = files |> Map.values() |> Enum.reject(&is_nil/1)
{:ok, zip_path} = ExPyshp.archive("output", "features", paths)

Read shapefiles from a ZIP. Extracted files are removed after the callback finishes:

ExPyshp.with_extract("features.zip", fn groups ->
  Enum.map(groups, fn group ->
    ExPyshp.read(group.shp, group.dbf, group.shx, group.prj)
  end)
end)

For a Phoenix %Plug.Upload{}, use upload.path in place of "features.zip".

See the API reference for other options and CHANGELOG.md for upgrade notes.