Current section

Files

Jump to
postal_code lib postal_code.ex
Raw

lib/postal_code.ex

defmodule PostalCode do
@moduledoc """
Postal code parser for the Norwegian postal code system.
"""
alias CSV
@posten_file Path.join(File.cwd!(), "postal_codes.csv")
|> Path.expand(__DIR__)
|> File.read!()
|> String.split("\n")
|> Enum.map(fn x -> x |> String.replace("\r", "") |> String.split("\t") end)
@erik_file Path.join(File.cwd!(), "postnummer.csv")
|> File.stream!()
|> CSV.decode()
|> Enum.map(fn x ->
case x do
{:ok, list} ->
Enum.at(
Enum.map(list, fn y ->
y |> String.replace("\r", "") |> String.split("\t")
end),
0
)
end
end)
def get_erik_file() do
@erik_file
end
@doc """
Accepts a postal code in string format, and returns the postal place.
## Examples
iex> PostalCode.postal_code_to_place("0186")
{:ok, "OSLO"}
iex> PostalCode.postal_code_to_place("0638")
:no_results
iex> PostalCode.postal_code_to_place(0186)
{:error, "Invalid type of postal code, must be string."}
iex> PostalCode.postal_code_to_place("018")
{:error, "Length must be 4."}
"""
def postal_code_to_place(postal_code)
when is_binary(postal_code) and bit_size(postal_code) == 32 do
case get_matching_postal_code(postal_code) do
[_postal_code, place, _county_code, _county_name, _category] ->
{:ok, place}
nil ->
:no_results
end
end
def postal_code_to_place(postal_code) when is_binary(postal_code),
do: {:error, :invalid_length}
def postal_code_to_place(_postal_code),
do: {:error, :invalid_type}
def postal_code_to_county(postal_code)
when is_binary(postal_code) and bit_size(postal_code) == 32 do
case get_matching_postal_code(postal_code, :erik) do
[_, _, _, _, _, _, _, _, county, latitude, longitude, _, _, _] ->
{:ok,
%{
"county" => county,
"location" => %{
"latitude" => latitude,
"longitude" => longitude
}
}}
nil ->
:no_results
end
end
def location_to_county(%{"latitude" => latitude, "longitude" => longitude}) do
results =
Enum.reduce(
@erik_file,
%{
"last_diff_long" => 99.99,
"last_diff_lat" => 99.99,
"longitude" => 0.0,
"latitude" => 0.0,
"county" => ""
},
fn [
_,
_,
_,
_,
_,
_,
_,
_,
county,
postal_lat,
postal_lon,
_,
_,
_
],
acc ->
with {proper_latitude, _} <- Float.parse(latitude),
{proper_longitude, _} <- Float.parse(longitude),
{proper_latitude_postal, _} <- Float.parse(postal_lat),
{proper_longitude_postal, _} <- Float.parse(postal_lon),
diff_latitude <- (proper_latitude - proper_latitude_postal) |> abs,
diff_longitude <- (proper_longitude - proper_longitude_postal) |> abs,
true <-
diff_latitude <= acc["last_diff_lat"] && diff_longitude <= acc["last_diff_long"] do
%{
"longitude" => proper_longitude_postal,
"latitude" => proper_latitude_postal,
"last_diff_long" => diff_longitude |> Float.round(4),
"last_diff_lat" => diff_latitude |> Float.round(4),
"county" => county
}
else
_ ->
acc
end
end
)
if(results["county"] !== "") do
{:ok, results["county"]}
else
{:error, :invalid}
end
end
def postal_code_to_county(postal_code) when is_binary(postal_code),
do: {:error, :invalid_length}
def postal_code_to_county(_), do: {:error, :invalid_type}
def postal_code_to_location(postal_code)
when is_binary(postal_code) and bit_size(postal_code) == 32 do
case get_matching_postal_code(postal_code, :erik) do
[_, _, _, _, _, _, _, _, _, latitude, longitude, _, _, _] ->
{latitude, longitude}
_ ->
:no_results
end
end
def postal_code_to_location(postal_code) when is_binary(postal_code),
do: {:error, "Length must be 4."}
def postal_code_to_location(_postal_code),
do: {:error, "Invalid type of postal code, must be string"}
defp get_matching_postal_code(postal_code) do
Enum.find(@posten_file, fn x -> Enum.at(x, 0) == postal_code end)
end
defp get_matching_postal_code(postal_code, :erik) do
Enum.find(@erik_file, fn x -> Enum.at(x, 0) == postal_code end)
end
end