Class: CocinaDisplay::Geospatial::BoundingBox

Inherits:
Coordinates
  • Object
show all
Defined in:
lib/cocina_display/geospatial.rb

Overview

A bounding box defined by its southwest and northeast corner points. The box can wrap east-west across the antimeridian, in which case its west edge is numerically east of its east edge. Both Solr's rectangle syntax and GeoJSON spell a crossing box that way, as do MARC 034 $d/$e, which are the westernmost and easternmost longitudes rather than the minimum and maximum.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Coordinates

from_cocina, from_structured_values, parse

Constructor Details

#initialize(southwest:, northeast:) ⇒ BoundingBox

Construct a BoundingBox from two corner Geo::Coord points.

Parameters:

  • southwest (Geo::Coord)
  • northeast (Geo::Coord)


214
215
216
217
# File 'lib/cocina_display/geospatial.rb', line 214

def initialize(southwest:, northeast:)
  @southwest = southwest
  @northeast = northeast
end

Instance Attribute Details

#northeastObject (readonly)

Returns the value of attribute northeast.



188
189
190
# File 'lib/cocina_display/geospatial.rb', line 188

def northeast
  @northeast
end

#southwestObject (readonly)

Returns the value of attribute southwest.



188
189
190
# File 'lib/cocina_display/geospatial.rb', line 188

def southwest
  @southwest
end

Class Method Details

.from_coords(west:, east:, north:, south:) ⇒ BoundingBox?

Construct a BoundingBox from west, east, north, and south string values. West and east are used as given, so a box that crosses the antimeridian is preserved instead of rejected.

Parameters:

  • west (String)

    western longitude

  • east (String)

    eastern longitude

  • north (String)

    northern latitude

  • south (String)

    southern latitude

Returns:



198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/cocina_display/geospatial.rb', line 198

def self.from_coords(west:, east:, north:, south:)
  southwest = Geo::Coord.parse("#{south}, #{west}")
  northeast = Geo::Coord.parse("#{north}, #{east}")

  # Must be parsable
  return unless southwest && northeast

  # A box can wrap east-west, but never north-south
  return if southwest.lat > northeast.lat

  new(southwest: southwest, northeast: northeast)
end

Instance Method Details

#as_bboxArray<Array<Float>>

Note:

Limits decimals to 6 places.

Note:

For a box crossing the antimeridian, east is carried past 180 so that the pair still reads southwest to northeast.

Format the bounding box as an array of two coordinate pairs [[S, W], [N, E]].

Examples:

[[34.0522, -118.2437], [34.1996, -117.9522]]

Returns:

  • (Array<Array<Float>>)


298
299
300
# File 'lib/cocina_display/geospatial.rb', line 298

def as_bbox
  [[south, west], [north, unwrapped_east]]
end

#as_envelopeString

Note:

Limits decimals to 6 places.

Note:

West is greater than east for a box crossing the antimeridian.

Format using the CQL ENVELOPE representation.

Examples:

"ENVELOPE(-118.2437, -117.9522, 34.1996, 34.0522)"

Returns:

  • (String)


278
279
280
# File 'lib/cocina_display/geospatial.rb', line 278

def as_envelope
  "ENVELOPE(%.6f, %.6f, %.6f, %.6f)" % [west, east, north, south]
end

#as_pointString

Note:

Limits decimals to 6 places.

The box center point as a space-separated x y (longitude latitude) pair.

Examples:

"-118.2437 34.0522"

Returns:

  • (String)


286
287
288
289
290
# File 'lib/cocina_display/geospatial.rb', line 286

def as_point
  center_lng = (west + unwrapped_east) / 2
  center_lng -= 360 if center_lng > 180
  "%.6f %.6f" % [center_lng, (south + north) / 2]
end

#as_wktString

Note:

Limits decimals to 6 places.

Note:

A box crossing the antimeridian is split into two polygons at the date line, so that every longitude stays within bounds.

Format using the Well-Known Text (WKT) representation.



267
268
269
270
271
# File 'lib/cocina_display/geospatial.rb', line 267

def as_wkt
  return "POLYGON(#{ring(west, east)})" unless crosses_antimeridian?

  "MULTIPOLYGON((#{ring(west, 180)}), (#{ring(-180, east)}))"
end

#crosses_antimeridian?Boolean

True if the box wraps east-west across the antimeridian.

Returns:

  • (Boolean)


245
246
247
# File 'lib/cocina_display/geospatial.rb', line 245

def crosses_antimeridian?
  west > east
end

#eastBigDecimal

The easternmost longitude of the box.

Returns:

  • (BigDecimal)


227
228
229
# File 'lib/cocina_display/geospatial.rb', line 227

def east
  northeast.lng
end

#northBigDecimal

The northernmost latitude of the box.

Returns:

  • (BigDecimal)


233
234
235
# File 'lib/cocina_display/geospatial.rb', line 233

def north
  northeast.lat
end

#southBigDecimal

The southernmost latitude of the box.

Returns:

  • (BigDecimal)


239
240
241
# File 'lib/cocina_display/geospatial.rb', line 239

def south
  southwest.lat
end

#to_sString

Note:

This format adapts the "Annex D" human representation style.

Format for display in DMS format, adapted from ISO 6709 standard.

Examples:

"118°14′37″W -- 117°56′55″W / 34°03′08″N -- 34°11′59″N"

Returns:

  • (String)

See Also:



254
255
256
257
258
# File 'lib/cocina_display/geospatial.rb', line 254

def to_s
  south_str, west_str = format_point(southwest)
  north_str, east_str = format_point(northeast)
  "#{west_str} -- #{east_str} / #{north_str} -- #{south_str}"
end

#westBigDecimal

The westernmost longitude of the box.

Returns:

  • (BigDecimal)


221
222
223
# File 'lib/cocina_display/geospatial.rb', line 221

def west
  southwest.lng
end