lib/decode_feature.ex

defmodule DecodeFeature do
  # TODO: This is not working yet, to be fixed in a future release
  def decode(%Geobuf.Data{data_type: {:feature_collection, feature_collection}}) do
    %{
      "type" => "FeatureCollection",
      "features" => Enum.map(feature_collection.features, &decode/1)
    }
  end

  def decode(%Geobuf.Data{data_type: {:feature, feature}} = data) do
    %{
      "properties" => decode_properties(feature.properties, feature.values, data.keys),
      "type" => "Feature",
      "geometry" => decode_geometry(feature.geometry, data.precision, data.dimensions)
    }
    |> maybe_add_id(feature.id_type)
  end

  def decode(%Geobuf.Data{data_type: {:geometry, geometry}} = data) do
    decode_geometry(geometry, data.precision, data.dimensions)
  end

  defp maybe_add_id(feature, id) do
    if id != nil do
      Map.put(feature, "id", decode_id(id))
    else
      feature
    end
  end

  defp decode_id(nil), do: nil
  defp decode_id({:id, id}), do: id

  defp decode_properties(properties, values, keys) do
    Enum.chunk_every(properties, 2)
    |> Enum.map(fn [key_idx, val_idx] ->
      {keys |> Enum.at(key_idx), values |> Enum.at(val_idx) |> decode_value()}
    end)
    |> Enum.into(%{})
  end

  defp decode_geometry(geo, precision, dimensions) do
    DecodeGeometry.decode(geo, precision, dimensions)
  end

  defp decode_value(%Geobuf.Data.Value{value_type: {type, value}}) do
    case type do
      :bool_value -> value
      :double_value -> value
      :string_value -> value
      :pos_int_value -> value
      :neg_int_value -> -value
      :json_value -> decode_json(value)
      _ -> nil
    end
  end

  defp decode_json(json_value) do
    case Jason.decode(json_value) do
      {:ok, m} -> m
      {:error, _} -> raise "Failed to decode JSON"
    end
  end
end