Files
grecia/spec/models/geojson_format_validator_spec.rb
Javi Martín 1f627d34f1 Make sure polygons contain valid rings
According to the GeoJSON specification [1]:

> * A linear ring is a closed LineString with four or more positions.
> * The first and last positions are equivalent, and they MUST contain
>   identical values; their representation SHOULD also be identical.
> (...)
> * For type "Polygon", the "coordinates" member MUST be an array of
>   linear ring coordinate arrays.

Note that, for simplicity, right now we aren't checking whether the
coordinates are defined counterclockwise for exterior rings and
clockwise for interior rings, which is what the specification expects.

[1] https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6
2024-12-23 17:35:33 +01:00

609 lines
15 KiB
Ruby

require "rails_helper"
describe GeojsonFormatValidator do
before do
dummy_model = Class.new do
include ActiveModel::Model
attr_accessor :geojson
validates :geojson, geojson_format: true
end
stub_const("DummyModel", dummy_model)
end
let(:record) { DummyModel.new }
it "is not valid with an empty hash" do
record.geojson = "{}"
expect(record).not_to be_valid
end
it "is not valid with arbitrary keys" do
record.geojson = '{ "invalid": "yes" }'
expect(record).not_to be_valid
end
it "is not valid without a type" do
record.geojson = '{ "coordinates": [1.23, 4.56] }'
expect(record).not_to be_valid
end
it "is not valid without a type but a geometry" do
record.geojson = '{ "geometry": { "type": "Point", "coordinates": [1.23, 4.56] } }'
expect(record).not_to be_valid
end
context "Point geometry" do
it "is not valid without coordinates" do
record.geojson = '{ "type": "Point" }'
expect(record).not_to be_valid
end
it "is not valid with only one the longitude" do
record.geojson = '{ "type": "Point", "coordinates": 1.23 }'
expect(record).not_to be_valid
end
it "is not valid with non-numerical coordinates" do
record.geojson = '{ "type": "Point", "coordinates": ["1.23", "4.56"] }'
expect(record).not_to be_valid
end
it "is not valid with 3-dimensional coordinates" do
record.geojson = '{ "type": "Point", "coordinates": [1.23, 4.56, 7.89] }'
expect(record).not_to be_valid
end
it "is not valid with multiple coordinates" do
record.geojson = '{ "type": "Point", "coordinates": [[1.23, 4.56], [7.89, 10.11]] }'
expect(record).not_to be_valid
end
it "is not valid with a longitude above 180" do
record.geojson = '{ "type": "Point", "coordinates": [180.01, 4.56] }'
expect(record).not_to be_valid
end
it "is not valid with a longitude below -180" do
record.geojson = '{ "type": "Point", "coordinates": [-180.01, 4.56] }'
expect(record).not_to be_valid
end
it "is not valid with a latitude above 90" do
record.geojson = '{ "type": "Point", "coordinates": [1.23, 90.01] }'
expect(record).not_to be_valid
end
it "is not valid with a latitude below -90" do
record.geojson = '{ "type": "Point", "coordinates": [1.23, -90.01] }'
expect(record).not_to be_valid
end
it "is valid with coordinates in the valid range" do
record.geojson = '{ "type": "Point", "coordinates": [1.23, 4.56] }'
expect(record).to be_valid
end
it "is valid with coordinates at the positive end of the range" do
record.geojson = '{ "type": "Point", "coordinates": [180.0, 90.0] }'
expect(record).to be_valid
end
it "is valid with coordinates at the negative end of the range" do
record.geojson = '{ "type": "Point", "coordinates": [-180.0, -90.0] }'
expect(record).to be_valid
end
end
context "LineString or MultiPoint geometry" do
it "is not valid with a one-dimensional array of coordinates" do
record.geojson = '{ "type": "LineString", "coordinates": [1.23, 4.56] }'
expect(record).not_to be_valid
record.geojson = '{ "type": "MultiPoint", "coordinates": [1.23, 4.56] }'
expect(record).not_to be_valid
end
it "is not valid when some coordinates are invalid" do
record.geojson = '{ "type": "LineString", "coordinates": [[1.23, 4.56], [180.01, 4.56]] }'
expect(record).not_to be_valid
record.geojson = '{ "type": "MultiPoint", "coordinates": [[1.23, 4.56], [180.01, 4.56]] }'
expect(record).not_to be_valid
end
it "is valid when all the coordinates are valid" do
record.geojson = '{ "type": "LineString", "coordinates": [[1.23, 4.56], [7.89, 4.56]] }'
expect(record).to be_valid
record.geojson = '{ "type": "MultiPoint", "coordinates": [[1.23, 4.56], [7.89, 4.56]] }'
expect(record).to be_valid
end
end
context "LineString geometry" do
it "is not valid with only one point" do
record.geojson = '{ "type": "LineString", "coordinates": [[1.23, 4.56]] }'
expect(record).not_to be_valid
end
end
context "MultiPoint geometry" do
it "is valid with only one point" do
record.geojson = '{ "type": "MultiPoint", "coordinates": [[1.23, 4.56]] }'
expect(record).to be_valid
end
end
context "Polygon or MultiLineString geometry" do
it "is not valid with a one-dimensional array of coordinates" do
record.geojson = '{ "type": "MultiLineString", "coordinates": [1.23, 4.56] }'
expect(record).not_to be_valid
record.geojson = '{ "type": "Polygon", "coordinates": [1.23, 4.56] }'
expect(record).not_to be_valid
end
it "is not valid with a two-dimensional array of coordinates" do
record.geojson = '{ "type": "MultiLineString", "coordinates": [[1.23, 4.56], [7.89, 4.56]] }'
expect(record).not_to be_valid
record.geojson = '{ "type": "Polygon", "coordinates": [[1.23, 4.56], [7.89, 4.56]] }'
expect(record).not_to be_valid
end
end
context "MultiLineString geometry" do
it "is valid with just one line" do
record.geojson = '{ "type": "MultiLineString", "coordinates": [[[1.23, 4.56], [7.89, 4.56]]] }'
expect(record).to be_valid
end
it "is valid with multiple valid lines" do
record.geojson = <<~JSON
{
"type": "MultiLineString",
"coordinates": [
[[1.23, 4.56], [7.89, 4.56]],
[[10.11, 12.13], [14.15, 16.17]]
]
}
JSON
expect(record).to be_valid
end
it "is not valid if some lines are invalid" do
record.geojson = <<~JSON
{
"type": "MultiLineString",
"coordinates": [
[[1.23, 4.56], [7.89, 4.56]],
[[10.11, 12.13]]
]
}
JSON
expect(record).not_to be_valid
end
end
context "Polygon geometry" do
it "is not valid with a ring having less than four elements" do
record.geojson = <<~JSON
{
"type": "Polygon",
"coordinates": [[
[1.23, 4.56],
[7.89, 10.11],
[1.23, 4.56]
]]
}
JSON
expect(record).not_to be_valid
end
it "is not valid with a ring which with different starting and end points" do
record.geojson = <<~JSON
{
"type": "Polygon",
"coordinates": [[
[1.23, 4.56],
[7.89, 10.11],
[12.13, 14.15],
[16.17, 18.19]
]]
}
JSON
expect(record).not_to be_valid
end
it "is valid with one valid ring" do
record.geojson = <<~JSON
{
"type": "Polygon",
"coordinates": [[
[1.23, 4.56],
[7.89, 10.11],
[12.13, 14.15],
[1.23, 4.56]
]]
}
JSON
expect(record).to be_valid
end
it "is valid with multiple valid rings" do
record.geojson = <<~JSON
{
"type": "Polygon",
"coordinates": [
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0]
],
[
[100.8, 0.8],
[100.8, 0.2],
[100.2, 0.2],
[100.2, 0.8],
[100.8, 0.8]
]
]
}
JSON
expect(record).to be_valid
end
it "is not valid with multiple rings if some rings are invalid" do
record.geojson = <<~JSON
{
"type": "Polygon",
"coordinates": [
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0]
],
[
[100.8, 0.8],
[100.8, 0.2],
[100.2, 0.2]
]
]
}
JSON
expect(record).not_to be_valid
end
end
context "MultiPolygon geometry" do
it "is not valid with a one-dimensional array of coordinates" do
record.geojson = '{ "type": "MultiPolygon", "coordinates": [1.23, 4.56] }'
expect(record).not_to be_valid
end
it "is not valid with a two-dimensional array of coordinates" do
record.geojson = '{ "type": "MultiPolygon", "coordinates": [[1.23, 4.56], [7.89, 4.56]] }'
expect(record).not_to be_valid
end
it "is not valid with a three-dimensional polygon coordinates array" do
record.geojson = <<~JSON
{
"type": "MultiPolygon",
"coordinates": [[
[1.23, 4.56],
[7.89, 10.11],
[12.13, 14.15],
[1.23, 4.56]
]]
}
JSON
expect(record).not_to be_valid
end
it "is valid with a valid polygon" do
record.geojson = <<~JSON
{
"type": "MultiPolygon",
"coordinates": [[[
[1.23, 4.56],
[7.89, 10.11],
[12.13, 14.15],
[1.23, 4.56]
]]]
}
JSON
expect(record).to be_valid
end
it "is valid with multiple valid polygons" do
record.geojson = <<~JSON
{
"type": "MultiPolygon",
"coordinates": [
[
[
[1.23, 4.56],
[7.89, 10.11],
[12.13, 14.15],
[1.23, 4.56]
]
],
[
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0]
],
[
[100.8, 0.8],
[100.8, 0.2],
[100.2, 0.2],
[100.2, 0.8],
[100.8, 0.8]
]
]
]
}
JSON
expect(record).to be_valid
end
it "is not valid with multiple polygons if some polygons are invalid" do
record.geojson = <<~JSON
{
"type": "MultiPolygon",
"coordinates": [
[
[
[1.23, 4.56],
[7.89, 10.11],
[12.13, 14.15],
[1.23, 4.56]
]
],
[
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0]
],
[
[100.8, 0.8],
[100.8, 0.2],
[100.2, 0.2]
]
]
]
}
JSON
expect(record).not_to be_valid
end
end
context "GeometryCollection" do
it "is not valid if it doesn't contain geometries" do
record.geojson = '{ "type": "GeometryCollection" }'
expect(record).not_to be_valid
end
it "is not valid if geometries is not an array" do
record.geojson = <<~JSON
{
"type": "GeometryCollection",
"geometries": { "type": "Point", "coordinates": [1.23, 4.56] }
}
JSON
expect(record).not_to be_valid
end
it "is valid if the array of geometries is empty" do
record.geojson = '{ "type": "GeometryCollection", "geometries": [] }'
expect(record).to be_valid
end
it "is valid if all geometries are valid" do
record.geojson = <<~JSON
{
"type": "GeometryCollection",
"geometries": [
{
"type": "Point",
"coordinates": [100.0, 0.0]
},
{
"type": "LineString",
"coordinates": [
[101.0, 0.0],
[102.0, 1.0]
]
}
]
}
JSON
expect(record).to be_valid
end
it "is not valid if some geometries are invalid" do
record.geojson = <<~JSON
{
"type": "GeometryCollection",
"geometries": [
{
"type": "Point",
"coordinates": [100.0, 0.0]
},
{
"type": "LineString",
"coordinates": [101.0, 0.0]
}
]
}
JSON
expect(record).not_to be_valid
end
end
context "Feature" do
it "is valid with a valid geometry" do
record.geojson = <<~JSON
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [1.23, 4.56]
}
}
JSON
expect(record).to be_valid
end
it "is not valid with a valid geometry" do
record.geojson = <<~JSON
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [1.23]
}
}
JSON
expect(record).not_to be_valid
end
end
context "FeatureCollection" do
it "is not valid without features" do
record.geojson = '{ "type": "FeatureCollection" }'
end
it "is not valid if features is not an array" do
record.geojson = <<~JSON
{
"type": "FeatureCollection",
"features": {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [1.23, 4.56]
}
}
}
JSON
end
it "is valid if the array of features is empty" do
record.geojson = '{ "type": "FeatureCollection", "features": [] }'
expect(record).to be_valid
end
it "is valid if all features are valid" do
record.geojson = <<~JSON
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [1.23, 4.56]
}
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [[101.0, 0.0], [102.0, 1.0]]
}
}
]
}
JSON
expect(record).to be_valid
end
it "is not valid if some features are invalid" do
record.geojson = <<~JSON
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [1.23, 4.56]
}
},
{
"type": "LineString",
"coordinates": [[101.0, 0.0], [102.0, 1.0]]
}
]
}
JSON
expect(record).not_to be_valid
end
end
end