defmodule Microwaveprop.Buildings.ParserTest do use ExUnit.Case, async: true alias Microwaveprop.Buildings.Parser describe "parse_tile/1" do test "parses a single Polygon feature" do path = create_gz(%{ "type" => "Feature", "properties" => %{"height" => 15.0}, "geometry" => %{ "type" => "Polygon", "coordinates" => [ [ [-97.0, 32.9], [-97.0, 33.0], [-96.9, 33.0], [-96.9, 32.9], [-97.0, 32.9] ] ] } }) records = path |> Parser.parse_tile() |> Enum.to_list() assert length(records) == 1 rec = hd(records) assert rec.height_m == 15.0 assert rec.centroid_lat > 32.9 assert rec.centroid_lat < 33.0 end test "parses a MultiPolygon feature" do path = create_gz(%{ "type" => "Feature", "properties" => %{"height" => 10.0}, "geometry" => %{ "type" => "MultiPolygon", "coordinates" => [ [ [ [-97.0, 32.9], [-97.0, 33.0], [-96.9, 33.0], [-96.9, 32.9], [-97.0, 32.9] ] ] ] } }) records = path |> Parser.parse_tile() |> Enum.to_list() assert length(records) == 1 assert hd(records).height_m == 10.0 end test "drops features with height -1.0 (unknown)" do path = create_gz(%{ "type" => "Feature", "properties" => %{"height" => -1.0}, "geometry" => %{ "type" => "Polygon", "coordinates" => [ [ [-97.0, 32.9], [-97.0, 33.0], [-96.9, 33.0], [-96.9, 32.9], [-97.0, 32.9] ] ] } }) records = path |> Parser.parse_tile() |> Enum.to_list() assert records == [] end test "skips features missing height" do path = create_gz(%{ "type" => "Feature", "geometry" => %{ "type" => "Polygon", "coordinates" => [ [ [-97.0, 32.9], [-97.0, 33.0], [-96.9, 33.0], [-96.9, 32.9], [-97.0, 32.9] ] ] } }) records = path |> Parser.parse_tile() |> Enum.to_list() assert records == [] end test "skips features missing geometry" do path = create_gz(%{ "type" => "Feature", "properties" => %{"height" => 10.0} }) records = path |> Parser.parse_tile() |> Enum.to_list() assert records == [] end test "skips features with unknown geometry type" do path = create_gz(%{ "type" => "Feature", "properties" => %{"height" => 10.0}, "geometry" => %{ "type" => "Point", "coordinates" => [-97.0, 32.9] } }) records = path |> Parser.parse_tile() |> Enum.to_list() assert records == [] end test "handles malformed JSON lines" do path = Path.join(System.tmp_dir!(), "bad_#{System.unique_integer([:positive])}.csv.gz") File.write!(path, "not json\n", [:compressed]) on_exit(fn -> File.rm(path) end) records = path |> Parser.parse_tile() |> Enum.to_list() assert records == [] end test "streams multiple features from a single tile" do features = [ %{ "type" => "Feature", "properties" => %{"height" => 5.0}, "geometry" => %{ "type" => "Polygon", "coordinates" => [[[-97.0, 33.0], [-97.0, 33.001], [-96.999, 33.001], [-96.999, 33.0], [-97.0, 33.0]]] } }, %{ "type" => "Feature", "properties" => %{"height" => 20.0}, "geometry" => %{ "type" => "Polygon", "coordinates" => [[[-96.8, 32.7], [-96.8, 32.701], [-96.799, 32.701], [-96.799, 32.7], [-96.8, 32.7]]] } } ] path = create_gz_lines(features) records = path |> Parser.parse_tile() |> Enum.to_list() assert length(records) == 2 heights = Enum.map(records, & &1.height_m) assert 5.0 in heights assert 20.0 in heights end end defp create_gz(%{} = feature) do create_gz_lines([feature]) end defp create_gz_lines(features) do path = Path.join(System.tmp_dir!(), "parser_test_#{System.unique_integer([:positive])}.csv.gz") body = Enum.map_join(features, "\n", &Jason.encode!/1) <> "\n" File.write!(path, body, [:compressed]) on_exit(fn -> File.rm(path) end) path end end