Skip to content

Commit

Permalink
Calculate the bounding box for any GeoJSON object
Browse files Browse the repository at this point in the history
  • Loading branch information
mamantoha committed Mar 31, 2024
1 parent be684c2 commit 66b84af
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 0 deletions.
96 changes: 96 additions & 0 deletions spec/bbox_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
require "./spec_helper"

describe GeoJSON do
pt = GeoJSON::Point.new([102.0, 0.5])

multi_pt = GeoJSON::MultiPoint.new([
[102.0, 0.5],
[103.0, 1.0],
])

line = GeoJSON::LineString.new([
[102.0, -10.0],
[103.0, 1.0],
[104.0, 0.0],
[130.0, 4.0],
])

poly = GeoJSON::Polygon.new([
[
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0],
[101.0, 0.0],
],
])

multi_line = GeoJSON::MultiLineString.new([
[
[100.0, 0.0],
[101.0, 1.0],
],
[
[102.0, 2.0],
[103.0, 3.0],
],
])

multi_poly = GeoJSON::MultiPolygon.new([
[
[
[102.0, 2.0],
[103.0, 2.0],
[103.0, 3.0],
[102.0, 3.0],
[102.0, 2.0],
],
],
[
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0],
],
[
[100.2, 0.2],
[100.8, 0.2],
[100.8, 0.8],
[100.2, 0.8],
[100.2, 0.2],
],
],
])

geometry_collection = GeoJSON::GeometryCollection.new([pt, line, poly, multi_line, multi_poly])

feature = GeoJSON::Feature.new(poly)

pt_feature = GeoJSON::Feature.new(pt)
line_feature = GeoJSON::Feature.new(line)
poly_feature = GeoJSON::Feature.new(poly)
multi_line_feature = GeoJSON::Feature.new(multi_line)
multi_poly_feature = GeoJSON::Feature.new(multi_poly)

feature_collection = GeoJSON::FeatureCollection.new([
pt_feature,
line_feature,
poly_feature,
multi_line_feature,
multi_poly_feature,
])

context ".bbox" do
it { pt.bbox.should eq([102, 0.5, 102, 0.5]) }
it { multi_pt.bbox.should eq([102.0, 0.5, 103.0, 1.0]) }
it { line.bbox.should eq([102, -10, 130, 4]) }
it { poly.bbox.should eq([100, 0, 101, 1]) }
it { multi_line.bbox.should eq([100, 0, 103, 3]) }
it { multi_poly.bbox.should eq([100, 0, 103, 3]) }
it { geometry_collection.bbox.should eq([100, -10, 130, 4]) }
it { feature.bbox.should eq([100, 0, 101, 1]) }
it { feature_collection.bbox.should eq([100, -10, 130, 4]) }
end
end
2 changes: 2 additions & 0 deletions src/geojson/coordinates.cr
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ module GeoJSON
end
end

delegate "[]", to: coordinates

# Gets this Coordinates' longitude in decimal degrees according to WGS84.
def longitude
coordinates[0]
Expand Down
6 changes: 6 additions & 0 deletions src/geojson/feature.cr
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,11 @@ module GeoJSON
# *properties*, *id*, and bounding box *bbox*.
def initialize(@geometry, @properties = nil, *, @id = nil, @bbox = nil)
end

def bbox
return @bbox if @bbox

geometry.try(&.bbox)
end
end
end
17 changes: 17 additions & 0 deletions src/geojson/feature_collection.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,22 @@ module GeoJSON
# Creates a new `FeatureCollection` with the given *features*.
def initialize(@features : Array(Feature), *, @bbox = nil)
end

def bbox
return @bbox if @bbox

result = [Float64::INFINITY, Float64::INFINITY, -Float64::INFINITY, -Float64::INFINITY]

features.each do |feature|
feature.bbox.try do |feature_bbox|
result[0] = feature_bbox[0] if result[0] > feature_bbox[0]
result[1] = feature_bbox[1] if result[1] > feature_bbox[1]
result[2] = feature_bbox[2] if result[2] < feature_bbox[2]
result[3] = feature_bbox[3] if result[3] < feature_bbox[3]
end
end

result
end
end
end
17 changes: 17 additions & 0 deletions src/geojson/geometry_collection.cr
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,22 @@ module GeoJSON

geometries
end

def bbox
return @bbox if @bbox

result = [Float64::INFINITY, Float64::INFINITY, -Float64::INFINITY, -Float64::INFINITY]

geometries.each do |object|
object.bbox.try do |object_bbox|
result[0] = object_bbox[0] if result[0] > object_bbox[0]
result[1] = object_bbox[1] if result[1] > object_bbox[1]
result[2] = object_bbox[2] if result[2] < object_bbox[2]
result[3] = object_bbox[3] if result[3] < object_bbox[3]
end
end

result
end
end
end
15 changes: 15 additions & 0 deletions src/geojson/line_string.cr
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ module GeoJSON
raise_if_invalid
end

def bbox
return @bbox if @bbox

result = [Float64::INFINITY, Float64::INFINITY, -Float64::INFINITY, -Float64::INFINITY]

@coordinates.each do |coord|
result[0] = coord[0] if result[0] > coord[0]
result[1] = coord[1] if result[1] > coord[1]
result[2] = coord[0] if result[2] < coord[0]
result[3] = coord[1] if result[3] < coord[1]
end

result
end

private def raise_if_invalid
if coordinates.size < 2
raise GeoJSON::Exception.new("a line needs to have two or more coordinates to be valid")
Expand Down
17 changes: 17 additions & 0 deletions src/geojson/multi_line_string.cr
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,22 @@ module GeoJSON
def <<(coordinate : Array(Array(Float64)))
@coordinates << LineString.new(coordinate).coordinates
end

def bbox
return @bbox if @bbox

result = [Float64::INFINITY, Float64::INFINITY, -Float64::INFINITY, -Float64::INFINITY]

@coordinates.each do |line|
line.each do |coord|
result[0] = coord[0] if result[0] > coord[0]
result[1] = coord[1] if result[1] > coord[1]
result[2] = coord[0] if result[2] < coord[0]
result[3] = coord[1] if result[3] < coord[1]
end
end

result
end
end
end
15 changes: 15 additions & 0 deletions src/geojson/multi_point.cr
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,20 @@ module GeoJSON
def <<(coordinate : Array(Float64))
@coordinates << Point.new(coordinate).coordinates
end

def bbox
return @bbox if @bbox

result = [Float64::INFINITY, Float64::INFINITY, -Float64::INFINITY, -Float64::INFINITY]

@coordinates.each do |coord|
result[0] = coord[0] if result[0] > coord[0]
result[1] = coord[1] if result[1] > coord[1]
result[2] = coord[0] if result[2] < coord[0]
result[3] = coord[1] if result[3] < coord[1]
end

result
end
end
end
19 changes: 19 additions & 0 deletions src/geojson/multi_polygon.cr
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,24 @@ module GeoJSON
def <<(coordinate : Array(Array(Array(Float64))))
@coordinates << Polygon.new(coordinate).coordinates
end

def bbox
return @bbox if @bbox

result = [Float64::INFINITY, Float64::INFINITY, -Float64::INFINITY, -Float64::INFINITY]

@coordinates.each do |polygon|
polygon.each do |ring|
ring.each do |coord|
result[0] = coord[0] if result[0] > coord[0]
result[1] = coord[1] if result[1] > coord[1]
result[2] = coord[0] if result[2] < coord[0]
result[3] = coord[1] if result[3] < coord[1]
end
end
end

result
end
end
end
3 changes: 3 additions & 0 deletions src/geojson/object.cr
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ module GeoJSON

abstract def type : String

# Calculates the bounding box for any GeoJSON object, including `FeatureCollection`.
abstract def bbox

use_json_discriminator "type", {
Point: Point,
MultiPoint: MultiPoint,
Expand Down
6 changes: 6 additions & 0 deletions src/geojson/point.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ module GeoJSON
@coordinates = GeoJSON::Coordinates.new(coordinates)
end

def bbox
return @bbox if @bbox

[coordinates[0], coordinates[1], coordinates[0], coordinates[1]]
end

# Gets this Point's longitude in decimal degrees according to WGS84.
delegate longitude, to: coordinates
# Gets this Point's latitude in decimal degrees according to WGS84.
Expand Down
17 changes: 17 additions & 0 deletions src/geojson/polygon.cr
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ module GeoJSON
raise_if_invalid
end

def bbox
return @bbox if @bbox

result = [Float64::INFINITY, Float64::INFINITY, -Float64::INFINITY, -Float64::INFINITY]

@coordinates.each do |ring|
ring.each do |coord|
result[0] = coord[0] if result[0] > coord[0]
result[1] = coord[1] if result[1] > coord[1]
result[2] = coord[0] if result[2] < coord[0]
result[3] = coord[1] if result[3] < coord[1]
end
end

result
end

private def raise_if_invalid
if coordinates.empty?
"a number was found where a coordinate array should have been found: this needs to be nested more deeply"
Expand Down

0 comments on commit 66b84af

Please sign in to comment.