Class: AIPP::Border

Inherits:
Object
  • Object
show all
Defined in:
lib/aipp/border.rb

Overview

Custom border geometries

The border consists of one ore more open or closed geometries which are defined by either a GeoJSON file or arrays of coordinate pairs.

Defined Under Namespace

Classes: Position

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(geometries) ⇒ Border

Returns a new instance of Border.



12
13
14
# File 'lib/aipp/border.rb', line 12

def initialize(geometries)
  @geometries = geometries
end

Instance Attribute Details

#geometriesArray<AIXM::XY> (readonly)

Returns:



10
11
12
# File 'lib/aipp/border.rb', line 10

def geometries
  @geometries
end

Class Method Details

.from_array(array) ⇒ Object

New border object from array of points

The array must contain coordinate tuples in geographical order as latitude longitude separated by whitespace and/or commas.

Examples:

border = AIPP::Border.from_array([["45.1201332 6.00953165", "45.12006703 6.01574774"]])
border.geometries
# => [[#<AIXM::XY 45.12013320N 006.00953165E>, <AIXM::XY 45.12006703N 006.01574774E>]]

Parameters:



71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/aipp/border.rb', line 71

def from_array(array)
  geometries = array.map do |collection|
    collection.map do |coordinates|
      lat, long = coordinates.split(/[\s,]+/)
      AIXM.xy(lat: lat.to_f, long: long.to_f)
    end
  end
  allocate.instance_eval do
    initialize(geometries)
    self
  end
end

.from_file(file) ⇒ Object

New border object from GeoJSON file

The border GeoJSON files must be a geometry collection of one or more line strings:

{
  "type": "GeometryCollection",
  "geometries": [
    {
      "type": "LineString",
      "coordinates": [
        [6.009531650000042, 45.12013319700009],
        [6.015747738000073, 45.12006702600007]
      ]
    }
  ]
}

Please note that GeoJSON orders coordinate tuples in mathematical order as [longitude, latitude]!

Examples:

border = AIPP::Border.from_file("/path/to/national_park.geojson")
border.geometries
# => [[#<AIXM::XY 45.12013320N 006.00953165E>, <AIXM::XY 45.12006703N 006.01574774E>]]

Parameters:

  • file (Pathname, String)

    GeoJSON file



46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/aipp/border.rb', line 46

def from_file(file)
  file = Pathname(file) unless file.is_a? Pathname
  fail(ArgumentError, "file must have extension .geojson") unless file.extname == '.geojson'
  geometries = JSON.load(file)['geometries'].map do |collection|
    collection['coordinates'].map do |long, lat|
      AIXM.xy(lat: lat, long: long)
    end
  end
  allocate.instance_eval do
    initialize(geometries)
    self
  end
end

Instance Method Details

#closed?(geometry_index:) ⇒ Boolean

Whether the given geometry is closed or not

A geometry is considered closed when it’s first coordinate equals the last coordinate.

Parameters:

  • geometry_index (Integer)

    geometry to check

Returns:

  • (Boolean)

    true if the geometry is closed or false otherwise



97
98
99
100
# File 'lib/aipp/border.rb', line 97

def closed?(geometry_index:)
  geometry = @geometries[geometry_index]
  geometry.first == geometry.last
end

#inspectString

Returns:



86
87
88
# File 'lib/aipp/border.rb', line 86

def inspect
  %Q(#<#{self.class} #{@geometries.count} geometries>)
end

#nearest(geometry_index: nil, xy:) ⇒ AIPP::Border::Position

Find a position on a geometry nearest to the given coordinates

Parameters:

  • geometry_index (Integer) (defaults to: nil)

    index of the geometry on which to search or nil to search on all geometries

  • xy (AIXM::XY)

    coordinates to approximate

Returns:



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/aipp/border.rb', line 108

def nearest(geometry_index: nil, xy:)
  position = nil
  min_distance = 21_000_000   # max distance on earth in meters
  @geometries.each.with_index do |geometry, g_index|
    next unless geometry_index.nil? || geometry_index == g_index
    geometry.each.with_index do |coordinates, c_index|
      distance = xy.distance(coordinates).dim
      if distance < min_distance
        position = Position.new(geometries: geometries, geometry_index: g_index, coordinates_index: c_index)
        min_distance = distance
      end
    end
  end
  position
end

#segment(from_position:, to_position:) ⇒ Array<AIXM::XY>

Get a segment of a geometry between the given starting and ending positions

The segment ends either at the given ending position or at the last coordinates of the geometry. However, if the geometry is closed, the segment always continues up to the given ending position.

Parameters:

Returns:

  • (Array<AIXM::XY>)

    array of coordinates describing the segment



134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/aipp/border.rb', line 134

def segment(from_position:, to_position:)
  fail(ArgumentError, "both positions must be on the same geometry") unless from_position.geometry_index == to_position.geometry_index
  geometry_index = from_position.geometry_index
  geometry = @geometries[geometry_index]
  if closed?(geometry_index: geometry_index)
    up = from_position.coordinates_index.upto(to_position.coordinates_index)
    down = from_position.coordinates_index.downto(0) + (geometry.count - 2).downto(to_position.coordinates_index)
    geometry.values_at(*(up.count < down.count ? up : down).to_a)
  else
    geometry.values_at(*from_position.coordinates_index.up_or_downto(to_position.coordinates_index).to_a)
  end
end