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

hrzndhrn / geometry / 01f2650ac9bc8a1183042e16bb39433a223a177e

pending completion
01f2650ac9bc8a1183042e16bb39433a223a177e

push

github

NickNeck
Remove credo:disable

1608 of 1671 relevant lines covered (96.23%)

27.83 hits per line

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

94.44
/lib/geometry/multi_line_string.ex
1
defmodule Geometry.MultiLineString do
2
  # This file is auto-generated by `mix geometry.gen`.
3
  # The ZM version of this file is used as a template.
4

5
  @moduledoc """
6
  A set of line-strings from type `Geometry.LineString`
7

8
  `MultiLineStringMZ` implements the protocols `Enumerable` and `Collectable`.
9

10
  ## Examples
11

12
      iex> Enum.map(
13
      ...>   MultiLineString.new([
14
      ...>     LineString.new([
15
      ...>       Point.new(1, 2),
16
      ...>       Point.new(3, 4)
17
      ...>     ]),
18
      ...>     LineString.new([
19
      ...>       Point.new(1, 2),
20
      ...>       Point.new(11, 12),
21
      ...>       Point.new(13, 14)
22
      ...>     ])
23
      ...>   ]),
24
      ...>   fn line_string -> length line_string end
25
      ...> )
26
      [2, 3]
27

28
      iex> Enum.into(
29
      ...>   [LineString.new([Point.new(1, 2), Point.new(5, 6)])],
30
      ...>   MultiLineString.new())
31
      %MultiLineString{
32
        line_strings:
33
        MapSet.new([
34
          [[1, 2], [5, 6]]
35
        ])
36
      }
37
  """
38

39
  alias Geometry.GeoJson
40
  alias Geometry.LineString
41
  alias Geometry.MultiLineString
42
  alias Geometry.Point
43
  alias Geometry.WKB
44
  alias Geometry.WKT
45

46
  defstruct line_strings: MapSet.new()
47

48
  @type t :: %MultiLineString{line_strings: MapSet.t(Geometry.coordinates())}
49

50
  @doc """
51
  Creates an empty `MultiLineString`.
52

53
  ## Examples
54

55
      iex> MultiLineString.new()
56
      %MultiLineString{line_strings: MapSet.new()}
57
  """
58
  @spec new :: t()
59
  def new, do: %MultiLineString{}
5✔
60

61
  @doc """
62
  Creates a `MultiLineString` from the given `Geometry.MultiLineString`s.
63

64
  ## Examples
65

66
      iex> MultiLineString.new([
67
      ...>   LineString.new([
68
      ...>     Point.new(1, 2),
69
      ...>     Point.new(2, 3),
70
      ...>     Point.new(3, 4)
71
      ...>   ]),
72
      ...>   LineString.new([
73
      ...>     Point.new(10, 20),
74
      ...>     Point.new(30, 40)
75
      ...>   ]),
76
      ...>   LineString.new([
77
      ...>     Point.new(10, 20),
78
      ...>     Point.new(30, 40)
79
      ...>   ])
80
      ...> ])
81
      %Geometry.MultiLineString{
82
        line_strings:
83
          MapSet.new([
84
            [[1, 2], [2, 3], [3, 4]],
85
            [[10, 20], [30, 40]]
86
          ])
87
      }
88

89
      iex> MultiLineString.new([])
90
      %MultiLineString{line_strings: MapSet.new()}
91
  """
92
  @spec new([LineString.t()]) :: t()
93
  def new([]), do: %MultiLineString{}
1✔
94

95
  def new(line_strings) do
96
    %MultiLineString{
20✔
97
      line_strings:
98
        Enum.into(line_strings, MapSet.new(), fn line_string -> line_string.points end)
34✔
99
    }
100
  end
101

102
  @doc """
103
  Returns `true` if the given `MultiLineString` is empty.
104

105
  ## Examples
106

107
      iex> MultiLineString.empty?(MultiLineString.new())
108
      true
109

110
      iex> MultiLineString.empty?(
111
      ...>   MultiLineString.new([
112
      ...>     LineString.new([Point.new(1, 2), Point.new(3, 4)])
113
      ...>   ])
114
      ...> )
115
      false
116
  """
117
  @spec empty?(t()) :: boolean
118
  def empty?(%MultiLineString{} = multi_line_string),
119
    do: Enum.empty?(multi_line_string.line_strings)
3✔
120

121
  @doc """
122
  Creates a `MultiLineString` from the given coordinates.
123

124
  ## Examples
125

126
      iex> MultiLineString.from_coordinates([
127
      ...>   [[-1, 1], [2, 2], [-3, 3]],
128
      ...>   [[-10, 10], [-20, 20]]
129
      ...> ])
130
      %MultiLineString{
131
        line_strings:
132
          MapSet.new([
133
            [[-1, 1], [2, 2], [-3, 3]],
134
            [[-10, 10], [-20, 20]]
135
          ])
136
      }
137
  """
138
  @spec from_coordinates([Geometry.coordinate()]) :: t()
139
  def from_coordinates(coordinates) do
140
    %MultiLineString{line_strings: MapSet.new(coordinates)}
15✔
141
  end
142

143
  @doc """
144
  Returns an `:ok` tuple with the `MultiLineString` from the given GeoJSON
145
  term. Otherwise returns an `:error` tuple.
146

147
  ## Examples
148

149
      iex> ~s(
150
      ...>   {
151
      ...>     "type": "MultiLineString",
152
      ...>     "coordinates": [
153
      ...>       [[-1, 1], [2, 2], [-3, 3]],
154
      ...>       [[-10, 10], [-20, 20]]
155
      ...>     ]
156
      ...>   }
157
      ...> )
158
      iex> |> Jason.decode!()
159
      iex> |> MultiLineString.from_geo_json()
160
      {:ok,
161
       %Geometry.MultiLineString{
162
         line_strings:
163
           MapSet.new([
164
             [[-10, 10], [-20, 20]],
165
             [[-1, 1], [2, 2], [-3, 3]]
166
           ])
167
       }}
168
  """
169
  @spec from_geo_json(Geometry.geo_json_term()) :: {:ok, t()} | Geometry.geo_json_error()
170
  def from_geo_json(json), do: GeoJson.to_multi_line_string(json, MultiLineString)
1✔
171

172
  @doc """
173
  The same as `from_geo_json/1`, but raises a `Geometry.Error` exception if it fails.
174
  """
175
  @spec from_geo_json!(Geometry.geo_json_term()) :: t()
176
  def from_geo_json!(json) do
177
    case GeoJson.to_multi_line_string(json, MultiLineString) do
2✔
178
      {:ok, geometry} -> geometry
1✔
179
      error -> raise Geometry.Error, error
1✔
180
    end
181
  end
182

183
  @doc """
184
  Returns the GeoJSON term of a `MultiLineString`.
185

186
  There are no guarantees about the order of line-strings in the returned
187
  `coordinates`.
188

189
  ## Examples
190

191
  ```elixir
192
  [
193
    [[-1, 1], [2, 2], [-3, 3]],
194
    [[-10, 10], [-20, 20]]
195
  ]
196
  |> MultiLineString.from_coordinates()
197
  MultiLineString.to_geo_json(
198
    MultiLineString.new([
199
      LineString.new([
200
        Point.new(-1, 1),
201
        Point.new(2, 2),
202
        Point.new(-3, 3)
203
      ]),
204
      LineString.new([
205
        Point.new(-10, 10),
206
        Point.new(-20, 20)
207
      ])
208
    ])
209
  )
210
  # =>
211
  # %{
212
  #   "type" => "MultiLineString",
213
  #   "coordinates" => [
214
  #     [[-1, 1], [2, 2], [-3, 3]],
215
  #     [[-10, 10], [-20, 20]]
216
  #   ]
217
  # }
218
  ```
219
  """
220
  @spec to_geo_json(t()) :: Geometry.geo_json_term()
221
  def to_geo_json(%MultiLineString{line_strings: line_strings}) do
222
    %{
1✔
223
      "type" => "MultiLineString",
224
      "coordinates" => MapSet.to_list(line_strings)
225
    }
226
  end
227

228
  @doc """
229
  Returns an `:ok` tuple with the `MultiLineString` from the given WKT string.
230
  Otherwise returns an `:error` tuple.
231

232
  If the geometry contains a SRID the id is added to the tuple.
233

234
  ## Examples
235

236
      iex> MultiLineString.from_wkt("
237
      ...>   SRID=1234;MultiLineString (
238
      ...>     (10 20, 20 10, 20 40),
239
      ...>     (40 30, 30 30)
240
      ...>   )
241
      ...> ")
242
      {:ok, {
243
        %MultiLineString{
244
          line_strings:
245
            MapSet.new([
246
              [[10, 20], [20, 10], [20, 40]],
247
              [[40, 30], [30, 30]]
248
            ])
249
        },
250
        1234
251
      }}
252

253
      iex> MultiLineString.from_wkt("MultiLineString EMPTY")
254
      {:ok, %MultiLineString{}}
255
  """
256
  @spec from_wkt(Geometry.wkt()) ::
257
          {:ok, t() | {t(), Geometry.srid()}} | Geometry.wkt_error()
258
  def from_wkt(wkt), do: WKT.to_geometry(wkt, MultiLineString)
2✔
259

260
  @doc """
261
  The same as `from_wkt/1`, but raises a `Geometry.Error` exception if it fails.
262
  """
263
  @spec from_wkt!(Geometry.wkt()) :: t() | {t(), Geometry.srid()}
264
  def from_wkt!(wkt) do
265
    case WKT.to_geometry(wkt, MultiLineString) do
3✔
266
      {:ok, geometry} -> geometry
2✔
267
      error -> raise Geometry.Error, error
1✔
268
    end
269
  end
270

271
  @doc """
272
  Returns the WKT representation for a `MultiLineString`. With option `:srid`
273
  an EWKT representation with the SRID is returned.
274

275
  There are no guarantees about the order of line-strings in the returned
276
  WKT-string.
277

278
  ## Examples
279

280
  ```elixir
281
  MultiLineString.to_wkt(MultiLineString.new())
282
  # => "MultiLineString EMPTY"
283

284
  MultiLineString.to_wkt(
285
    MultiLineString.new([
286
      LineString(
287
        [Point.new(7.1, 8.1), Point.new(9.2, 5.2)]
288
      ),
289
      LineString(
290
        [Point.new(5.5, 9.2), Point.new(1.2, 3.2)]
291
      )
292
    ])
293
  )
294
  # Returns a string without any \\n or extra spaces (formatted just for readability):
295
  # MultiLineString (
296
  #   (5.5 9.2, 1.2 3.2),
297
  #   (7.1 8.1, 9.2 5.2)
298
  # )
299

300
  MultiLineString.to_wkt(
301
    MultiLineString.new([
302
      LineString(
303
        [Point.new(7.1, 8.1), Point.new(9.2, 5.2)]
304
      ),
305
      LineString(
306
        [Point.new(5.5, 9.2), Point.new(1.2, 3.2)]
307
      )
308
    ]),
309
    srid: 555
310
  )
311
  # Returns a string without any \\n or extra spaces (formatted just for readability):
312
  # SRID=555;MultiLineString (
313
  #   (5.5 9.2, 1.2 3.2),
314
  #   (7.1 8.1, 9.2 5.2)
315
  # )
316
  ```
317
  """
318
  @spec to_wkt(t(), opts) :: Geometry.wkt()
319
        when opts: [srid: Geometry.srid()]
320
  def to_wkt(%MultiLineString{line_strings: line_strings}, opts \\ []) do
321
    WKT.to_ewkt(
3✔
322
      <<
323
        "MultiLineString ",
324
        line_strings |> MapSet.to_list() |> to_wkt_line_strings()::binary
325
      >>,
326
      opts
327
    )
328
  end
329

330
  @doc """
331
  Returns the WKB representation for a `MultiLineString`.
332

333
  With option `:srid` an EWKB representation with the SRID is returned.
334

335
  The option `endian` indicates whether `:xdr` big endian or `:ndr` little
336
  endian is returned. The default is `:xdr`.
337

338
  The `:mode` determines whether a hex-string or binary is returned. The default
339
  is `:binary`.
340

341
  An example of a simpler geometry can be found in the description for the
342
  `Geometry.Point.to_wkb/1` function.
343
  """
344
  @spec to_wkb(t(), opts) :: Geometry.wkb()
345
        when opts: [endian: Geometry.endian(), srid: Geometry.srid(), mode: Geometry.mode()]
346
  def to_wkb(%MultiLineString{} = multi_line_string, opts \\ []) do
347
    endian = Keyword.get(opts, :endian, Geometry.default_endian())
8✔
348
    mode = Keyword.get(opts, :mode, Geometry.default_mode())
8✔
349
    srid = Keyword.get(opts, :srid)
8✔
350

351
    to_wkb(multi_line_string, srid, endian, mode)
8✔
352
  end
353

354
  @doc """
355
  Returns an `:ok` tuple with the `MultiLineString` from the given WKB string. Otherwise
356
  returns an `:error` tuple.
357

358
  If the geometry contains a SRID the id is added to the tuple.
359

360
  An example of a simpler geometry can be found in the description for the
361
  `Geometry.Point.from_wkb/2` function.
362
  """
363
  @spec from_wkb(Geometry.wkb(), Geometry.mode()) ::
364
          {:ok, t() | {t(), Geometry.srid()}} | Geometry.wkb_error()
365
  def from_wkb(wkb, mode \\ :binary), do: WKB.to_geometry(wkb, mode, MultiLineString)
366

367
  @doc """
368
  The same as `from_wkb/2`, but raises a `Geometry.Error` exception if it fails.
369
  """
370
  @spec from_wkb!(Geometry.wkb(), Geometry.mode()) :: t() | {t(), Geometry.srid()}
371
  def from_wkb!(wkb, mode \\ :binary) do
372
    case WKB.to_geometry(wkb, mode, MultiLineString) do
6✔
373
      {:ok, geometry} -> geometry
4✔
374
      error -> raise Geometry.Error, error
2✔
375
    end
376
  end
377

378
  @doc """
379
  Returns the number of elements in `MultiLineString`.
380

381
  ## Examples
382

383
      iex> MultiLineString.size(
384
      ...>   MultiLineString.new([
385
      ...>     LineString.new([
386
      ...>       Point.new(11, 12),
387
      ...>       Point.new(21, 22)
388
      ...>     ]),
389
      ...>     LineString.new([
390
      ...>       Point.new(31, 32),
391
      ...>       Point.new(41, 42)
392
      ...>     ])
393
      ...>   ])
394
      ...> )
395
      2
396
  """
397
  @spec size(t()) :: non_neg_integer()
398
  def size(%MultiLineString{line_strings: line_strings}), do: MapSet.size(line_strings)
2✔
399

400
  @doc """
401
  Checks if `MultiLineString` contains `line_string`.
402

403
  ## Examples
404

405
      iex> MultiLineString.member?(
406
      ...>   MultiLineString.new([
407
      ...>     LineString.new([
408
      ...>       Point.new(11, 12),
409
      ...>       Point.new(21, 22)
410
      ...>     ]),
411
      ...>     LineString.new([
412
      ...>       Point.new(31, 32),
413
      ...>       Point.new(41, 42)
414
      ...>     ])
415
      ...>   ]),
416
      ...>   LineString.new([
417
      ...>     Point.new(31, 32),
418
      ...>     Point.new(41, 42)
419
      ...>   ])
420
      ...> )
421
      true
422

423
      iex> MultiLineString.member?(
424
      ...>   MultiLineString.new([
425
      ...>     LineString.new([
426
      ...>       Point.new(11, 12),
427
      ...>       Point.new(21, 22)
428
      ...>     ]),
429
      ...>     LineString.new([
430
      ...>       Point.new(31, 32),
431
      ...>       Point.new(41, 42)
432
      ...>     ])
433
      ...>   ]),
434
      ...>   LineString.new([
435
      ...>     Point.new(11, 12),
436
      ...>     Point.new(41, 42)
437
      ...>   ])
438
      ...> )
439
      false
440
  """
441
  @spec member?(t(), LineString.t()) :: boolean()
442
  def member?(%MultiLineString{line_strings: line_strings}, %LineString{points: points}) do
443
    MapSet.member?(line_strings, points)
2✔
444
  end
445

446
  @doc """
447
  Converts `MultiLineString` to a list.
448
  """
449
  @spec to_list(t()) :: [Point.t()]
450
  def to_list(%MultiLineString{line_strings: line_strings}), do: MapSet.to_list(line_strings)
2✔
451

452
  @compile {:inline, to_wkt_line_strings: 1}
453
  defp to_wkt_line_strings([]), do: "EMPTY"
1✔
454

455
  defp to_wkt_line_strings([line_string | line_strings]) do
456
    <<"(",
2✔
457
      Enum.reduce(line_strings, LineString.to_wkt_points(line_string), fn line_string, acc ->
458
        <<acc::binary, ", ", LineString.to_wkt_points(line_string)::binary>>
2✔
459
      end)::binary, ")">>
460
  end
461

462
  @doc false
463
  @compile {:inline, to_wkb: 4}
464
  @spec to_wkb(t(), srid, endian, mode) :: wkb
465
        when srid: Geometry.srid() | nil,
466
             endian: Geometry.endian(),
467
             mode: Geometry.mode(),
468
             wkb: Geometry.wkb()
469
  def to_wkb(%MultiLineString{line_strings: line_strings}, srid, endian, mode) do
470
    <<
8✔
471
      WKB.byte_order(endian, mode)::binary,
472
      wkb_code(endian, not is_nil(srid), mode)::binary,
473
      WKB.srid(srid, endian, mode)::binary,
474
      to_wkb_line_strings(line_strings, endian, mode)::binary
475
    >>
476
  end
477

478
  @compile {:inline, to_wkb_line_strings: 3}
479
  defp to_wkb_line_strings(line_strings, endian, mode) do
480
    Enum.reduce(line_strings, WKB.length(line_strings, endian, mode), fn line_string, acc ->
8✔
481
      <<acc::binary, LineString.to_wkb(line_string, nil, endian, mode)::binary>>
10✔
482
    end)
483
  end
484

485
  @compile {:inline, wkb_code: 3}
486
  defp wkb_code(endian, srid?, :hex) do
487
    case {endian, srid?} do
4✔
488
      {:xdr, false} -> "00000005"
1✔
489
      {:ndr, false} -> "05000000"
1✔
490
      {:xdr, true} -> "20000005"
1✔
491
      {:ndr, true} -> "05000020"
1✔
492
    end
493
  end
494

495
  defp wkb_code(endian, srid?, :binary) do
496
    case {endian, srid?} do
4✔
497
      {:xdr, false} -> <<0x00000005::big-integer-size(32)>>
1✔
498
      {:ndr, false} -> <<0x00000005::little-integer-size(32)>>
1✔
499
      {:xdr, true} -> <<0x20000005::big-integer-size(32)>>
1✔
500
      {:ndr, true} -> <<0x20000005::little-integer-size(32)>>
1✔
501
    end
502
  end
503

504
  defimpl Enumerable do
505
    def count(multi_line_string) do
×
506
      {:ok, MultiLineString.size(multi_line_string)}
507
    end
508

509
    def member?(multi_line_string, val) do
×
510
      {:ok, MultiLineString.member?(multi_line_string, val)}
511
    end
512

513
    if function_exported?(Enumerable.List, :slice, 4) do
514
      def slice(multi_line_string) do
515
        size = MultiLineString.size(multi_line_string)
516

517
        {:ok, size,
518
         &Enumerable.List.slice(MultiLineString.to_list(multi_line_string), &1, &2, size)}
519
      end
520
    else
521
      def slice(multi_line_string) do
522
        size = MultiLineString.size(multi_line_string)
1✔
523

524
        {:ok, size, &MultiLineString.to_list/1}
1✔
525
      end
526
    end
527

528
    def reduce(multi_line_string, acc, fun) do
529
      Enumerable.List.reduce(MultiLineString.to_list(multi_line_string), acc, fun)
1✔
530
    end
531
  end
532

533
  defimpl Collectable do
534
    def into(%MultiLineString{line_strings: line_strings}) do
535
      fun = fn
1✔
536
        list, {:cont, x} ->
1✔
537
          [{x, []} | list]
538

539
        list, :done ->
540
          map =
1✔
541
            Map.merge(
542
              line_strings.map,
1✔
543
              Enum.into(list, %{}, fn {line_string, []} -> {line_string.points, []} end)
1✔
544
            )
545

546
          %MultiLineString{line_strings: %{line_strings | map: map}}
1✔
547

548
        _list, :halt ->
×
549
          :ok
550
      end
551

552
      {[], fun}
553
    end
554
  end
555
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