diff --git a/spec/bbox_spec.cr b/spec/bbox_spec.cr new file mode 100644 index 0000000..0c91f8f --- /dev/null +++ b/spec/bbox_spec.cr @@ -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 diff --git a/src/geojson/coordinates.cr b/src/geojson/coordinates.cr index 3ce745d..800b8dc 100644 --- a/src/geojson/coordinates.cr +++ b/src/geojson/coordinates.cr @@ -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] diff --git a/src/geojson/feature.cr b/src/geojson/feature.cr index d8a158f..3f78109 100644 --- a/src/geojson/feature.cr +++ b/src/geojson/feature.cr @@ -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 diff --git a/src/geojson/feature_collection.cr b/src/geojson/feature_collection.cr index 69708c3..6ad9b57 100644 --- a/src/geojson/feature_collection.cr +++ b/src/geojson/feature_collection.cr @@ -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 diff --git a/src/geojson/geometry_collection.cr b/src/geojson/geometry_collection.cr index 0aad723..0506400 100644 --- a/src/geojson/geometry_collection.cr +++ b/src/geojson/geometry_collection.cr @@ -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 diff --git a/src/geojson/line_string.cr b/src/geojson/line_string.cr index 1f8bd10..df178d9 100644 --- a/src/geojson/line_string.cr +++ b/src/geojson/line_string.cr @@ -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") diff --git a/src/geojson/multi_line_string.cr b/src/geojson/multi_line_string.cr index f44aa67..445d960 100644 --- a/src/geojson/multi_line_string.cr +++ b/src/geojson/multi_line_string.cr @@ -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 diff --git a/src/geojson/multi_point.cr b/src/geojson/multi_point.cr index db2d71d..49f0d43 100644 --- a/src/geojson/multi_point.cr +++ b/src/geojson/multi_point.cr @@ -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 diff --git a/src/geojson/multi_polygon.cr b/src/geojson/multi_polygon.cr index 7c18926..07e0a22 100644 --- a/src/geojson/multi_polygon.cr +++ b/src/geojson/multi_polygon.cr @@ -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 diff --git a/src/geojson/object.cr b/src/geojson/object.cr index 36c5ca0..5b51644 100644 --- a/src/geojson/object.cr +++ b/src/geojson/object.cr @@ -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, diff --git a/src/geojson/point.cr b/src/geojson/point.cr index 8c10972..d823c5f 100644 --- a/src/geojson/point.cr +++ b/src/geojson/point.cr @@ -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. diff --git a/src/geojson/polygon.cr b/src/geojson/polygon.cr index 7e7e488..0239493 100644 --- a/src/geojson/polygon.cr +++ b/src/geojson/polygon.cr @@ -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"