• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hrzndhrn / geometry / 28d4bbb100e6b3d960890012336345736b1222a0

21 Feb 2026 04:10PM UTC coverage: 97.806% (-1.1%) from 98.913%
28d4bbb100e6b3d960890012336345736b1222a0

Pull #22

github

web-flow
Merge pull request #23 from hrzndhrn/compound-curve

Add geometry type CompoundCurve
Pull Request #22: Release 1.2.0

80 of 87 new or added lines in 12 files covered. (91.95%)

10 existing lines in 1 file now uncovered.

535 of 547 relevant lines covered (97.81%)

54.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.24
/lib/geometry/decoder/wkb.ex
1
defmodule Geometry.Decoder.WKB do
2
  @moduledoc false
3

4
  alias Geometry.DecodeError
5

6
  alias Geometry.CircularString
7
  alias Geometry.CircularStringM
8
  alias Geometry.CircularStringZ
9
  alias Geometry.CircularStringZM
10
  alias Geometry.CompoundCurve
11
  alias Geometry.CompoundCurveM
12
  alias Geometry.CompoundCurveZ
13
  alias Geometry.CompoundCurveZM
14
  alias Geometry.GeometryCollection
15
  alias Geometry.GeometryCollectionM
16
  alias Geometry.GeometryCollectionZ
17
  alias Geometry.GeometryCollectionZM
18
  alias Geometry.LineString
19
  alias Geometry.LineStringM
20
  alias Geometry.LineStringZ
21
  alias Geometry.LineStringZM
22
  alias Geometry.MultiLineString
23
  alias Geometry.MultiLineStringM
24
  alias Geometry.MultiLineStringZ
25
  alias Geometry.MultiLineStringZM
26
  alias Geometry.MultiPoint
27
  alias Geometry.MultiPointM
28
  alias Geometry.MultiPointZ
29
  alias Geometry.MultiPointZM
30
  alias Geometry.MultiPolygon
31
  alias Geometry.MultiPolygonM
32
  alias Geometry.MultiPolygonZ
33
  alias Geometry.MultiPolygonZM
34
  alias Geometry.Point
35
  alias Geometry.PointM
36
  alias Geometry.PointZ
37
  alias Geometry.PointZM
38
  alias Geometry.Polygon
39
  alias Geometry.PolygonM
40
  alias Geometry.PolygonZ
41
  alias Geometry.PolygonZM
42

43
  @spec decode(Geometry.wkb()) :: {:ok, Geometry.t()} | {:error, DecodeError.t()}
44
  geos =
45
    [
46
      :point,
47
      :line_string,
48
      :polygon,
49
      :multi_point,
50
      :multi_line_string,
51
      :multi_polygon,
52
      :geometry_collection,
53
      :circular_string,
54
      :compound_curve
55
    ]
56
    |> Enum.with_index(1)
57
    |> Enum.flat_map(fn {type, index} ->
58
      [
59
        %{code: 0x00000000 + index, type: type, dim: :xy, srid?: false},
60
        %{code: 0x20000000 + index, type: type, dim: :xy, srid?: true},
61
        %{code: 0x40000000 + index, type: type, dim: :xym, srid?: false},
62
        %{code: 0x60000000 + index, type: type, dim: :xym, srid?: true},
63
        %{code: 0x80000000 + index, type: type, dim: :xyz, srid?: false},
64
        %{code: 0xA0000000 + index, type: type, dim: :xyz, srid?: true},
65
        %{code: 0xC0000000 + index, type: type, dim: :xyzm, srid?: false},
66
        %{code: 0xE0000000 + index, type: type, dim: :xyzm, srid?: true}
67
      ]
68
    end)
69
    |> Enum.flat_map(fn data ->
70
      [
71
        Map.merge(data, %{
72
          flag: 0,
73
          mod: quote(do: big),
74
          endian: :xdr,
75
          empty: <<127, 248, 0, 0, 0, 0, 0, 0>>
76
        }),
77
        Map.merge(data, %{
78
          flag: 1,
79
          mod: quote(do: little),
80
          endian: :ndr,
81
          empty: <<0, 0, 0, 0, 0, 0, 248, 127>>
82
        })
83
      ]
84
    end)
85

86
  for geo <- geos do
87
    if geo.srid? do
88
      cond do
89
        geo.type == :point && geo.dim == :xy ->
90
          def decode(
8✔
91
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
92
                  <<srid::unquote(geo.mod)-integer-size(32)>>, x::unquote(geo.mod)-float-size(64),
93
                  y::unquote(geo.mod)-float-size(64)>>
94
              ) do
95
            {:ok, Point.new(x, y, srid)}
96
          end
97

98
          def decode(
6✔
99
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
100
                  <<srid::unquote(geo.mod)-integer-size(32)>>, unquote(geo.empty),
101
                  unquote(geo.empty)>>
102
              ) do
103
            {:ok, Map.put(Point.new(), :srid, srid)}
104
          end
105

106
        geo.type == :point && geo.dim == :xym ->
107
          def decode(
6✔
108
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
109
                  <<srid::unquote(geo.mod)-integer-size(32)>>, x::unquote(geo.mod)-float-size(64),
110
                  y::unquote(geo.mod)-float-size(64), m::unquote(geo.mod)-float-size(64)>>
111
              ) do
112
            {:ok, PointM.new(x, y, m, srid)}
113
          end
114

115
          def decode(
6✔
116
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
117
                  <<srid::unquote(geo.mod)-integer-size(32)>>, unquote(geo.empty),
118
                  unquote(geo.empty), unquote(geo.empty)>>
119
              ) do
120
            {:ok, Map.put(PointM.new(), :srid, srid)}
121
          end
122

123
        geo.type == :point && geo.dim == :xyz ->
124
          def decode(
6✔
125
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
126
                  <<srid::unquote(geo.mod)-integer-size(32)>>, x::unquote(geo.mod)-float-size(64),
127
                  y::unquote(geo.mod)-float-size(64), z::unquote(geo.mod)-float-size(64)>>
128
              ) do
129
            {:ok, PointZ.new(x, y, z, srid)}
130
          end
131

132
          def decode(
6✔
133
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
134
                  <<srid::unquote(geo.mod)-integer-size(32)>>, unquote(geo.empty),
135
                  unquote(geo.empty), unquote(geo.empty)>>
136
              ) do
137
            {:ok, Map.put(PointZ.new(), :srid, srid)}
138
          end
139

140
        geo.type == :point && geo.dim == :xyzm ->
141
          def decode(
6✔
142
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
143
                  <<srid::unquote(geo.mod)-integer-size(32)>>, x::unquote(geo.mod)-float-size(64),
144
                  y::unquote(geo.mod)-float-size(64), z::unquote(geo.mod)-float-size(64),
145
                  m::unquote(geo.mod)-float-size(64)>>
146
              ) do
147
            {:ok, PointZM.new(x, y, z, m, srid)}
148
          end
149

150
          def decode(
6✔
151
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
152
                  <<srid::unquote(geo.mod)-integer-size(32)>>, unquote(geo.empty),
153
                  unquote(geo.empty), unquote(geo.empty), unquote(geo.empty)>>
154
              ) do
155
            {:ok, Map.put(PointZM.new(), :srid, srid)}
156
          end
157

158
        true ->
159
          def decode(
160
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
161
                  <<srid::unquote(geo.mod)-integer-size(32)>>, rest::bits>> = bin
162
              ) do
163
            with {:ok, geometry, rest} <-
182✔
164
                   unquote(geo.type)(unquote(geo.dim), unquote(geo.endian), srid, rest),
165
                 <<>> <- rest do
130✔
166
              {:ok, geometry}
167
            else
168
              error -> handle_error(error, bin)
52✔
169
            end
170
          end
171
      end
172
    else
173
      cond do
174
        geo.type == :point && geo.dim == :xy ->
175
          def decode(
7✔
176
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
177
                  x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64)>>
178
              ) do
179
            {:ok, Point.new(x, y)}
180
          end
181

182
          def decode(
6✔
183
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
184
                  unquote(geo.empty), unquote(geo.empty)>>
185
              ) do
186
            {:ok, Point.new()}
187
          end
188

189
        geo.type == :point && geo.dim == :xym ->
190
          def decode(
7✔
191
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
192
                  x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64),
193
                  m::unquote(geo.mod)-float-size(64)>>
194
              ) do
195
            {:ok, PointM.new(x, y, m)}
196
          end
197

198
          def decode(
6✔
199
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
200
                  unquote(geo.empty), unquote(geo.empty), unquote(geo.empty)>>
201
              ) do
202
            {:ok, PointM.new()}
203
          end
204

205
        geo.type == :point && geo.dim == :xyz ->
206
          def decode(
9✔
207
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
208
                  x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64),
209
                  z::unquote(geo.mod)-float-size(64)>>
210
              ) do
211
            {:ok, PointZ.new(x, y, z)}
212
          end
213

214
          def decode(
6✔
215
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
216
                  unquote(geo.empty), unquote(geo.empty), unquote(geo.empty)>>
217
              ) do
218
            {:ok, PointZ.new()}
219
          end
220

221
        geo.type == :point && geo.dim == :xyzm ->
222
          def decode(
7✔
223
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
224
                  x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64),
225
                  z::unquote(geo.mod)-float-size(64), m::unquote(geo.mod)-float-size(64)>>
226
              ) do
227
            {:ok, PointZM.new(x, y, z, m)}
228
          end
229

230
          def decode(
6✔
231
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
232
                  unquote(geo.empty), unquote(geo.empty), unquote(geo.empty), unquote(geo.empty)>>
233
              ) do
234
            {:ok, PointZM.new()}
235
          end
236

237
        true ->
238
          def decode(
239
                <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
240
                  rest::bits>> = bin
241
              ) do
242
            with {:ok, geometry, rest} <-
377✔
243
                   unquote(geo.type)(unquote(geo.dim), unquote(geo.endian), 0, rest),
244
                 <<>> <- rest do
216✔
245
              {:ok, geometry}
246
            else
247
              error -> handle_error(error, bin)
185✔
248
            end
249
          end
250
      end
251
    end
252
  end
253

254
  def decode(<<>>) do
3✔
255
    {:error, %DecodeError{from: :wkb, reason: :empty}}
256
  end
257

258
  for geo <- geos do
259
    if geo.srid? do
260
      def decode(
261
            <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
262
              _srid::unquote(geo.mod)-integer-size(32), rest::bits>> = bin
263
          ) do
264
        handle_error(:invalid_data, rest, bin)
12✔
265
      end
266

267
      def decode(
268
            <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
269
              rest::bits>> = bin
270
          ) do
271
        handle_error(:invalid_srid, rest, bin)
3✔
272
      end
273
    else
274
      def decode(
275
            <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32),
276
              rest::bits>> = bin
277
          ) do
278
        handle_error(:invalid_data, rest, bin)
3✔
279
      end
280
    end
281
  end
282

283
  def decode(<<endian::8, _rest::bits>> = bin) when endian not in [0, 1] do
284
    handle_error([expected_endian: :flag], bin, bin)
8✔
285
  end
286

287
  def decode(<<_endian::8, rest::bits>> = bin) do
288
    handle_error(:expected_geometry_code, rest, bin)
3✔
289
  end
290

291
  def decode(input) when not is_binary(input) do
3✔
292
    {:error, %DecodeError{from: :wkb, reason: :no_binary, rest: input}}
293
  end
294

295
  for geo <- geos, !geo.srid? do
296
    defp collection(
297
           unquote(geo.dim),
298
           unquote(geo.endian),
299
           <<unquote(geo.flag), unquote(geo.code)::unquote(geo.mod)-integer-size(32), rest::bits>>
300
         ) do
301
      {:ok, unquote(geo.type), rest}
143✔
302
    end
303
  end
304

305
  defp collection(_dim, endian, <<flag::8, rest::bits>> = bin) do
306
    if (endian == :xdr && flag == 0) || (endian == :ndr && flag == 1) do
14✔
307
      {:error, :expected_geometry_code, rest}
10✔
308
    else
309
      {:error, [expected_endian: endian], bin}
4✔
310
    end
311
  end
312

313
  endian_code_bin = %{
314
    {:ndr, :point, :xy} => <<1, 1, 0, 0, 0>>,
315
    {:ndr, :point, :xym} => <<1, 1, 0, 0, 64>>,
316
    {:ndr, :point, :xyz} => <<1, 1, 0, 0, 128>>,
317
    {:ndr, :point, :xyzm} => <<1, 1, 0, 0, 192>>,
318
    {:xdr, :point, :xy} => <<0, 0, 0, 0, 1>>,
319
    {:xdr, :point, :xym} => <<0, 64, 0, 0, 1>>,
320
    {:xdr, :point, :xyz} => <<0, 128, 0, 0, 1>>,
321
    {:xdr, :point, :xyzm} => <<0, 192, 0, 0, 1>>,
322
    {:ndr, :line_string, :xy} => <<1, 2, 0, 0, 0>>,
323
    {:ndr, :line_string, :xym} => <<1, 2, 0, 0, 64>>,
324
    {:ndr, :line_string, :xyz} => <<1, 2, 0, 0, 128>>,
325
    {:ndr, :line_string, :xyzm} => <<1, 2, 0, 0, 192>>,
326
    {:xdr, :line_string, :xy} => <<0, 0, 0, 0, 2>>,
327
    {:xdr, :line_string, :xym} => <<0, 64, 0, 0, 2>>,
328
    {:xdr, :line_string, :xyz} => <<0, 128, 0, 0, 2>>,
329
    {:xdr, :line_string, :xyzm} => <<0, 192, 0, 0, 2>>,
330
    {:ndr, :polygon, :xy} => <<1, 3, 0, 0, 0>>,
331
    {:ndr, :polygon, :xym} => <<1, 3, 0, 0, 64>>,
332
    {:ndr, :polygon, :xyz} => <<1, 3, 0, 0, 128>>,
333
    {:ndr, :polygon, :xyzm} => <<1, 3, 0, 0, 192>>,
334
    {:xdr, :polygon, :xy} => <<0, 0, 0, 0, 3>>,
335
    {:xdr, :polygon, :xym} => <<0, 64, 0, 0, 3>>,
336
    {:xdr, :polygon, :xyz} => <<0, 128, 0, 0, 3>>,
337
    {:xdr, :polygon, :xyzm} => <<0, 192, 0, 0, 3>>,
338
    {:ndr, :circular_string, :xy} => <<1, 8, 0, 0, 0>>,
339
    {:ndr, :circular_string, :xym} => <<1, 8, 0, 0, 64>>,
340
    {:ndr, :circular_string, :xyz} => <<1, 8, 0, 0, 128>>,
341
    {:ndr, :circular_string, :xyzm} => <<1, 8, 0, 0, 192>>,
342
    {:xdr, :circular_string, :xy} => <<0, 0, 0, 0, 8>>,
343
    {:xdr, :circular_string, :xym} => <<0, 64, 0, 0, 8>>,
344
    {:xdr, :circular_string, :xyz} => <<0, 128, 0, 0, 8>>,
345
    {:xdr, :circular_string, :xyzm} => <<0, 192, 0, 0, 8>>
346
  }
347

348
  for geo <- geos, geo.type == :point, geo.srid? do
349
    defp coordinates(
350
           unquote(geo.dim),
351
           unquote(geo.endian),
352
           <<length::unquote(geo.mod)-integer-size(32), bin::bits>>
353
         ) do
354
      try do
366✔
355
        {data, rest} =
366✔
356
          Enum.map_reduce(List.duplicate(0, length), bin, fn _ignore, bin ->
357
            case coordinate(unquote(geo.dim), unquote(geo.endian), bin) do
1,301✔
358
              {:ok, data, bin} ->
1,280✔
359
                {data, bin}
360

361
              error ->
362
                throw(error)
21✔
363
            end
364
          end)
365

366
        {:ok, data, rest}
345✔
367
      catch
368
        error -> error
21✔
369
      end
370
    end
371

372
    defp coordinates(unquote(geo.dim), unquote(geo.endian), bin) do
373
      {:error, :invalid_length, bin}
8✔
374
    end
375

376
    defp multi_coordinates(
377
           unquote(geo.dim),
378
           unquote(geo.endian),
379
           <<length::unquote(geo.mod)-integer-size(32), bin::bits>>
380
         ) do
381
      try do
109✔
382
        {data, rest} =
109✔
383
          Enum.map_reduce(List.duplicate(0, length), bin, fn _ignore, bin ->
384
            case coordinates(unquote(geo.dim), unquote(geo.endian), bin) do
145✔
385
              {:ok, data, bin} ->
141✔
386
                {data, bin}
387

388
              error ->
389
                throw(error)
4✔
390
            end
391
          end)
392

393
        {:ok, data, rest}
105✔
394
      catch
395
        error -> error
4✔
396
      end
397
    end
398

399
    defp multi_coordinates(unquote(geo.dim), unquote(geo.endian), bin) do
400
      {:error, :invalid_length, bin}
4✔
401
    end
402

403
    defp line_string(
404
           unquote(geo.dim),
405
           unquote(geo.endian),
406
           srid,
407
           <<length::unquote(geo.mod)-integer-size(32), bin::bits>>
408
         ) do
409
      try do
170✔
410
        {data, rest} =
170✔
411
          Enum.map_reduce(List.duplicate(0, length), bin, fn _ignore, bin ->
412
            case coordinate(unquote(geo.dim), unquote(geo.endian), bin) do
332✔
413
              {:ok, data, bin} ->
311✔
414
                {data, bin}
415

416
              error ->
417
                throw(error)
21✔
418
            end
419
          end)
420

421
        line_string = %unquote(
149✔
422
            case geo.dim do
423
              :xy -> LineString
424
              :xym -> LineStringM
425
              :xyz -> LineStringZ
426
              :xyzm -> LineStringZM
427
            end
428
          ){
429
          path: data,
430
          srid: srid
431
        }
432

433
        {:ok, line_string, rest}
149✔
434
      catch
435
        error -> error
21✔
436
      end
437
    end
438

439
    defp line_string(unquote(geo.dim), unquote(geo.endian), _srid, bin) do
440
      {:error, :invalid_length, bin}
8✔
441
    end
442

443
    defp circular_string(
444
           unquote(geo.dim),
445
           unquote(geo.endian),
446
           srid,
447
           <<length::unquote(geo.mod)-integer-size(32), bin::bits>>
448
         ) do
449
      try do
100✔
450
        {data, rest} =
100✔
451
          Enum.map_reduce(List.duplicate(0, length), bin, fn _ignore, bin ->
452
            case coordinate(unquote(geo.dim), unquote(geo.endian), bin) do
281✔
453
              {:ok, data, bin} ->
265✔
454
                {data, bin}
455

456
              error ->
457
                throw(error)
16✔
458
            end
459
          end)
460

461
        circular_string = %unquote(
84✔
462
            case geo.dim do
463
              :xy -> CircularString
464
              :xym -> CircularStringM
465
              :xyz -> CircularStringZ
466
              :xyzm -> CircularStringZM
467
            end
468
          ){
469
          arcs: data,
470
          srid: srid
471
        }
472

473
        {:ok, circular_string, rest}
84✔
474
      catch
475
        error -> error
16✔
476
      end
477
    end
478

479
    defp circular_string(unquote(geo.dim), unquote(geo.endian), _srid, bin) do
480
      {:error, :invalid_length, bin}
8✔
481
    end
482

483
    defp compound_curve(
484
           unquote(geo.dim),
485
           unquote(geo.endian),
486
           srid,
487
           <<length::unquote(geo.mod)-integer-size(32), bin::bits>>
488
         ) do
489
      try do
61✔
490
        {data, rest} =
61✔
491
          compound_curve_segments(unquote(geo.dim), unquote(geo.endian), srid, length, bin, [])
492

493
        compound_curve = %unquote(
48✔
494
            case geo.dim do
495
              :xy -> CompoundCurve
496
              :xym -> CompoundCurveM
497
              :xyz -> CompoundCurveZ
498
              :xyzm -> CompoundCurveZM
499
            end
500
          ){
501
          segments: data,
502
          srid: srid
503
        }
504

505
        {:ok, compound_curve, rest}
48✔
506
      catch
507
        error -> error
13✔
508
      end
509
    end
510

511
    defp compound_curve(unquote(geo.dim), unquote(geo.endian), _srid, bin) do
512
      {:error, :invalid_length, bin}
8✔
513
    end
514

515
    if geo.dim == :xy && geo.endian == :xdr do
516
      defp compound_curve_segments(_dim, _endian, _srid, 0, bin, acc) do
48✔
517
        {Enum.reverse(acc), bin}
518
      end
519
    end
520

521
    defp compound_curve_segments(unquote(geo.dim), unquote(geo.endian), srid, length, bin, acc) do
522
      {geometry, bin} =
117✔
523
        case bin do
524
          <<unquote(endian_code_bin[{geo.endian, :line_string, geo.dim}]), bin::bits>> ->
525
            case line_string(unquote(geo.dim), unquote(geo.endian), srid, bin) do
72✔
526
              {:ok, line_string, bin} ->
68✔
527
                {line_string, bin}
528

529
              error ->
530
                throw(error)
4✔
531
            end
532

533
          <<unquote(endian_code_bin[{geo.endian, :circular_string, geo.dim}]), bin::bits>> ->
534
            case circular_string(unquote(geo.dim), unquote(geo.endian), srid, bin) do
36✔
535
              {:ok, circular_string, bin} ->
36✔
536
                {circular_string, bin}
537

538
              error ->
NEW
539
                throw(error)
×
540
            end
541

542
          bin ->
543
            throw({:error, :expected_compound_curve_segment, bin})
9✔
544
        end
545

546
      compound_curve_segments(
104✔
547
        unquote(geo.dim),
548
        unquote(geo.endian),
549
        srid,
550
        length - 1,
551
        bin,
552
        [geometry | acc]
553
      )
554
    end
555

556
    defp polygon(
557
           unquote(geo.dim),
558
           unquote(geo.endian),
559
           srid,
560
           <<length::unquote(geo.mod)-integer-size(32), bin::bits>>
561
         ) do
562
      try do
90✔
563
        {data, rest} =
90✔
564
          Enum.map_reduce(List.duplicate(0, length), bin, fn _ignore, bin ->
565
            case coordinates(unquote(geo.dim), unquote(geo.endian), bin) do
146✔
566
              {:ok, data, bin} ->
129✔
567
                {data, bin}
568

569
              error ->
570
                throw(error)
17✔
571
            end
572
          end)
573

574
        polygon = %unquote(
73✔
575
            case geo.dim do
576
              :xy -> Polygon
577
              :xym -> PolygonM
578
              :xyz -> PolygonZ
579
              :xyzm -> PolygonZM
580
            end
581
          ){
582
          rings: data,
583
          srid: srid
584
        }
585

586
        {:ok, polygon, rest}
73✔
587
      catch
588
        error -> error
17✔
589
      end
590
    end
591

592
    defp polygon(unquote(geo.dim), unquote(geo.endian), _srid, bin) do
593
      {:error, :invalid_length, bin}
8✔
594
    end
595

596
    defp multi_point(
597
           unquote(geo.dim),
598
           unquote(geo.endian),
599
           srid,
600
           <<length::unquote(geo.mod)-integer-size(32), bin::bits>>
601
         ) do
602
      try do
73✔
603
        {data, rest} =
73✔
604
          Enum.map_reduce(List.duplicate(0, length), bin, fn
605
            _ignore, <<unquote(endian_code_bin[{geo.endian, :point, geo.dim}]), bin::bits>> ->
606
              case coordinate(unquote(geo.dim), unquote(geo.endian), bin) do
81✔
607
                {:ok, data, bin} ->
73✔
608
                  {data, bin}
609

610
                error ->
611
                  throw(error)
8✔
612
              end
613

614
            _ignore, bin ->
615
              case bin do
24✔
616
                <<flag::8, bin::bits>> when flag == unquote(geo.flag) ->
617
                  throw({:error, :expected_geometry_code, bin})
8✔
618

619
                bin ->
620
                  throw({:error, [expected_endian: unquote(geo.endian)], bin})
16✔
621
              end
622
          end)
623

624
        multi_point = %unquote(
41✔
625
            case geo.dim do
626
              :xy -> MultiPoint
627
              :xym -> MultiPointM
628
              :xyz -> MultiPointZ
629
              :xyzm -> MultiPointZM
630
            end
631
          ){
632
          points: data,
633
          srid: srid
634
        }
635

636
        {:ok, multi_point, rest}
41✔
637
      catch
638
        error -> error
32✔
639
      end
640
    end
641

642
    defp multi_point(unquote(geo.dim), unquote(geo.endian), _srid, bin) do
643
      {:error, :invalid_length, bin}
8✔
644
    end
645

646
    defp multi_line_string(
647
           unquote(geo.dim),
648
           unquote(geo.endian),
649
           srid,
650
           <<length::unquote(geo.mod)-integer-size(32), bin::bits>>
651
         ) do
652
      try do
71✔
653
        {data, rest} =
71✔
654
          Enum.map_reduce(List.duplicate(0, length), bin, fn
655
            _ignore,
656
            <<unquote(endian_code_bin[{geo.endian, :line_string, geo.dim}]), bin::bits>> ->
657
              case coordinates(unquote(geo.dim), unquote(geo.endian), bin) do
83✔
658
                {:ok, data, bin} ->
75✔
659
                  {data, bin}
660

661
                error ->
662
                  throw(error)
8✔
663
              end
664

665
            _ignore, bin ->
666
              case bin do
20✔
667
                <<flag::8, bin::bits>> when flag == unquote(geo.flag) ->
668
                  throw({:error, :expected_geometry_code, bin})
8✔
669

670
                bin ->
671
                  throw({:error, [expected_endian: unquote(geo.endian)], bin})
12✔
672
              end
673
          end)
674

675
        multi_line_string = %unquote(
43✔
676
            case geo.dim do
677
              :xy -> MultiLineString
678
              :xym -> MultiLineStringM
679
              :xyz -> MultiLineStringZ
680
              :xyzm -> MultiLineStringZM
681
            end
682
          ){
683
          line_strings: data,
684
          srid: srid
685
        }
686

687
        {:ok, multi_line_string, rest}
43✔
688
      catch
689
        error -> error
28✔
690
      end
691
    end
692

693
    defp multi_line_string(unquote(geo.dim), unquote(geo.endian), _srid, bin) do
694
      {:error, :invalid_length, bin}
8✔
695
    end
696

697
    defp multi_polygon(
698
           unquote(geo.dim),
699
           unquote(geo.endian),
700
           srid,
701
           <<length::unquote(geo.mod)-integer-size(32), bin::bits>>
702
         ) do
703
      try do
57✔
704
        {data, rest} =
57✔
705
          Enum.map_reduce(List.duplicate(0, length), bin, fn
706
            _ignore, <<unquote(endian_code_bin[{geo.endian, :polygon, geo.dim}]), bin::bits>> ->
707
              case multi_coordinates(unquote(geo.dim), unquote(geo.endian), bin) do
113✔
708
                {:ok, data, bin} ->
105✔
709
                  {data, bin}
710

711
                error ->
712
                  throw(error)
8✔
713
              end
714

715
            _ignore, bin ->
716
              case bin do
8✔
717
                <<flag::8, bin::bits>> when flag == unquote(geo.flag) ->
718
                  throw({:error, :expected_geometry_code, bin})
4✔
719

720
                bin ->
721
                  throw({:error, [expected_endian: unquote(geo.endian)], bin})
4✔
722
              end
723
          end)
724

725
        multi_polygon = %unquote(
41✔
726
            case geo.dim do
727
              :xy -> MultiPolygon
728
              :xym -> MultiPolygonM
729
              :xyz -> MultiPolygonZ
730
              :xyzm -> MultiPolygonZM
731
            end
732
          ){
733
          polygons: data,
734
          srid: srid
735
        }
736

737
        {:ok, multi_polygon, rest}
41✔
738
      catch
739
        error -> error
16✔
740
      end
741
    end
742

743
    defp multi_polygon(unquote(geo.dim), unquote(geo.endian), _srid, bin) do
744
      {:error, :invalid_length, bin}
4✔
745
    end
746

747
    defp geometry_collection(
748
           unquote(geo.dim),
749
           unquote(geo.endian),
750
           srid,
751
           <<length::unquote(geo.mod)-integer-size(32), bin::bits>>
752
         ) do
753
      try do
63✔
754
        {data, rest} =
63✔
755
          Enum.map_reduce(List.duplicate(0, length), bin, fn _ignore, bin ->
756
            case collection(unquote(geo.dim), unquote(geo.endian), bin) do
157✔
757
              {:ok, :geometry_collection, bin} ->
758
                case geometry_collection(unquote(geo.dim), unquote(geo.endian), srid, bin) do
1✔
759
                  {:ok, geometry_collection, bin} -> {geometry_collection, bin}
1✔
UNCOV
760
                  error -> throw(error)
×
761
                end
762

763
              {:ok, :line_string, bin} ->
764
                case line_string(unquote(geo.dim), unquote(geo.endian), srid, bin) do
34✔
765
                  {:ok, line_string, bin} -> {line_string, bin}
33✔
766
                  error -> throw(error)
1✔
767
                end
768

769
              {:ok, :multi_line_string, bin} ->
770
                case multi_line_string(unquote(geo.dim), unquote(geo.endian), srid, bin) do
3✔
771
                  {:ok, multi_line_string, bin} -> {multi_line_string, bin}
3✔
UNCOV
772
                  error -> throw(error)
×
773
                end
774

775
              {:ok, :multi_point, bin} ->
776
                case multi_point(unquote(geo.dim), unquote(geo.endian), srid, bin) do
1✔
777
                  {:ok, multi_point, bin} -> {multi_point, bin}
1✔
UNCOV
778
                  error -> throw(error)
×
779
                end
780

781
              {:ok, :multi_polygon, bin} ->
782
                case multi_polygon(unquote(geo.dim), unquote(geo.endian), srid, bin) do
1✔
783
                  {:ok, multi_polygon, bin} -> {multi_polygon, bin}
1✔
UNCOV
784
                  error -> throw(error)
×
785
                end
786

787
              {:ok, :point, bin} ->
788
                case point(unquote(geo.dim), unquote(geo.endian), srid, bin) do
69✔
789
                  {:ok, point, bin} -> {point, bin}
65✔
790
                  error -> throw(error)
4✔
791
                end
792

793
              {:ok, :polygon, bin} ->
794
                case polygon(unquote(geo.dim), unquote(geo.endian), srid, bin) do
34✔
795
                  {:ok, polygon, bin} -> {polygon, bin}
33✔
796
                  error -> throw(error)
1✔
797
                end
798

799
              {:ok, :circular_string, bin} ->
NEW
UNCOV
800
                case circular_string(unquote(geo.dim), unquote(geo.endian), srid, bin) do
×
NEW
UNCOV
801
                  {:ok, circular_string, bin} -> {circular_string, bin}
×
NEW
UNCOV
802
                  error -> throw(error)
×
803
                end
804

805
              {:ok, :compound_curve, bin} ->
NEW
UNCOV
806
                case compound_curve(unquote(geo.dim), unquote(geo.endian), srid, bin) do
×
NEW
UNCOV
807
                  {:ok, compound_curve, bin} -> {compound_curve, bin}
×
NEW
UNCOV
808
                  error -> throw(error)
×
809
                end
810

811
              error ->
812
                throw(error)
14✔
813
            end
814
          end)
815

816
        geometry_collection = %unquote(
43✔
817
            case geo.dim do
818
              :xy -> GeometryCollection
819
              :xym -> GeometryCollectionM
820
              :xyz -> GeometryCollectionZ
821
              :xyzm -> GeometryCollectionZM
822
            end
823
          ){
824
          geometries: data,
825
          srid: srid
826
        }
827

828
        {:ok, geometry_collection, rest}
43✔
829
      catch
830
        error -> error
20✔
831
      end
832
    end
833

834
    defp geometry_collection(unquote(geo.dim), unquote(geo.endian), _srid, bin) do
835
      {:error, :invalid_length, bin}
4✔
836
    end
837
  end
838

839
  for geo <- geos, geo.type == :point, geo.srid?, geo.dim == :xy do
840
    defp coordinate(:xy, unquote(geo.endian), bin) do
841
      case bin do
513✔
842
        <<x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64), bin::bits>> ->
843
          {:ok, [x, y], bin}
495✔
844

845
        _error ->
846
          {:error, :invalid_coordinate, bin}
18✔
847
      end
848
    end
849

850
    defp coordinate(dim, unquote(geo.endian), bin) when dim in [:xym, :xyz] do
851
      case bin do
988✔
852
        <<x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64),
853
          z::unquote(geo.mod)-float-size(64), bin::bits>> ->
854
          {:ok, [x, y, z], bin}
956✔
855

856
        _error ->
857
          {:error, :invalid_coordinate, bin}
32✔
858
      end
859
    end
860

861
    defp coordinate(:xyzm, unquote(geo.endian), bin) do
862
      case bin do
494✔
863
        <<x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64),
864
          z::unquote(geo.mod)-float-size(64), m::unquote(geo.mod)-float-size(64), bin::bits>> ->
865
          {:ok, [x, y, z, m], bin}
478✔
866

867
        _error ->
868
          {:error, :invalid_coordinate, bin}
16✔
869
      end
870
    end
871

872
    # The point functions are just used for geometry collections. A geometry
873
    # point is detected in the decode function.
874
    defp point(:xy, unquote(geo.endian), srid, bin) do
875
      case bin do
18✔
876
        <<x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64), bin::bits>> ->
877
          {:ok, Point.new(x, y, srid), bin}
9✔
878

879
        <<unquote(geo.empty), unquote(geo.empty), bin::bits>> ->
880
          {:ok, Point.new([], srid), bin}
8✔
881

882
        _error ->
883
          {:error, :invalid_coordinate, bin}
1✔
884
      end
885
    end
886

887
    defp point(:xym, unquote(geo.endian), srid, bin) do
888
      case bin do
17✔
889
        <<x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64),
890
          m::unquote(geo.mod)-float-size(64), bin::bits>> ->
891
          {:ok, PointM.new(x, y, m, srid), bin}
8✔
892

893
        <<unquote(geo.empty), unquote(geo.empty), unquote(geo.empty), bin::bits>> ->
894
          {:ok, PointM.new([], srid), bin}
8✔
895

896
        _error ->
897
          {:error, :invalid_coordinate, bin}
1✔
898
      end
899
    end
900

901
    defp point(:xyz, unquote(geo.endian), srid, bin) do
902
      case bin do
17✔
903
        <<x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64),
904
          z::unquote(geo.mod)-float-size(64), bin::bits>> ->
905
          {:ok, PointZ.new(x, y, z, srid), bin}
8✔
906

907
        <<unquote(geo.empty), unquote(geo.empty), unquote(geo.empty), bin::bits>> ->
908
          {:ok, PointZ.new([], srid), bin}
8✔
909

910
        _error ->
911
          {:error, :invalid_coordinate, bin}
1✔
912
      end
913
    end
914

915
    defp point(:xyzm, unquote(geo.endian), srid, bin) do
916
      case bin do
17✔
917
        <<x::unquote(geo.mod)-float-size(64), y::unquote(geo.mod)-float-size(64),
918
          z::unquote(geo.mod)-float-size(64), m::unquote(geo.mod)-float-size(64), bin::bits>> ->
919
          {:ok, PointZM.new(x, y, z, m, srid), bin}
8✔
920

921
        <<unquote(geo.empty), unquote(geo.empty), unquote(geo.empty), unquote(geo.empty),
922
          bin::bits>> ->
923
          {:ok, PointZM.new([], srid), bin}
8✔
924

925
        _error ->
926
          {:error, :invalid_coordinate, bin}
1✔
927
      end
928
    end
929
  end
930

931
  defp handle_error({:error, reason, rest}, data) do
213✔
932
    {:error,
933
     %DecodeError{
934
       from: :wkb,
935
       reason: reason,
936
       rest: rest,
937
       offset: byte_size(data) - byte_size(rest)
938
     }}
939
  end
940

941
  defp handle_error(rest, data) when is_binary(rest) and is_binary(data) do
24✔
942
    {:error,
943
     %DecodeError{
944
       from: :wkb,
945
       reason: :eos,
946
       rest: rest,
947
       offset: byte_size(data) - byte_size(rest)
948
     }}
949
  end
950

951
  defp handle_error(reason, rest, data) do
29✔
952
    {:error,
953
     %DecodeError{
954
       from: :wkb,
955
       reason: reason,
956
       rest: rest,
957
       offset: byte_size(data) - byte_size(rest)
958
     }}
959
  end
960
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc