Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calculate the bounding box for any GeoJSON object #11

Merged
merged 1 commit into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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