Skip to content

geocrystal/geojson

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

95 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GeoJSON

Crystal CI Docs License

Crystal library for reading and writing GeoJSON

This library contains:

  • Functions for encoding and decoding GeoJSON formatted data
  • Classes for all GeoJSON Objects
  • Allow "foreign members" in a GeoJSON Objects

Installation

Add the dependency to your shard.yml:

dependencies:
  geojson:
    github: geocrystal/geojson

and run shards install

require "geojson"

Position

A position is the fundamental geometry construct. The coordinates member of a Geometry object is composed of either:

  • one position in the case of a Point geometry
  • an array of positions in the case of a LineString or MultiPoint geometry
  • an array of LineString or linear ring coordinates in the case of a Polygon or MultiLineString geometry
  • an array of Polygon coordinates in the case of a MultiPolygon geometry

A position is an array of Float64.

There must be two or more elements. The first two elements are longitude and latitude. Altitude may be included as an optional third element.

postition = [-80.1347334, 25.7663562, 0.0]
point = GeoJSON::Point.new(position)

GeoJSON types

Point

A GeoJSON point looks like this:

{
  "type": "Point",
  "coordinates": [-80.1347334, 25.7663562]
}

It is important to note that coordinates is in the format [longitude, latitude]. Longitude comes before latitude in GeoJSON.

For type Point, the coordinates member is a single position.

Serialize geometry type:

point = GeoJSON::Point.new([-80.1347334, 25.7663562])
json = point.to_json
# => {"type":"Point","coordinates":[-80.1347334,25.7663562]}

Deserialize geometry type:

point = GeoJSON::Point.from_json(json)
# => #<GeoJSON::Point:0x7f1444af9920>
point.longitude
# => -80.1347334
point.latitude
# => 25.7663562

MultiPoint

For type MultiPoint, the coordinates member is an array of positions.

point1 = GeoJSON::Point.new(longitude: 100.0, latitude: 0.0)
point2 = GeoJSON::Point.new(longitude: 101.0, latitude: 1.0)

multi_point = GeoJSON::MultiPoint.new([point1, point2])
multi_point.to_json
{
  "type":"MultiPoint",
  "coordinates":[[100.0, 0.0], [101.0, 1.0]]
}

LineString

For type LineString, the coordinates member is an array of two or more positions.

line_string = GeoJSON::LineString.new [[-124.2, 42.0], [-120.0, 42.0]]
line_string.to_json
{
  "type": "LineString",
  "coordinates": [[-124.2, 42.0], [-120.0, 42.0]]
}

MultiLineString

For type MultiLineString, the coordinates member is an array of LineString coordinate arrays.

line_string1 = GeoJSON::LineString.new([[100.0, 0.0], [101.0, 1.0]])
line_string2 = GeoJSON::LineString.new([[102.0, 2.0], [103.0, 3.0]])

multi_line_string = GeoJSON::MultiLineString.new([line_string1, line_string2])
multi_line_string = GeoJSON::MultiLineString.new([
  [[100.0, 0.0], [101.0, 1.0]],
  [[102.0, 2.0], [103.0, 3.0]],
])
multi_line_string.to_json
{
  "type":"MultiLineString",
  "coordinates":[
    [
      [100.0, 0.0],
      [101.0, 1.0]
    ],
    [
      [102.0, 2.0],
      [103.0, 3.0]
    ]
  ]
}

Polygon

GeoJSON polygons represent closed shapes on a map, like triangles, squares, dodecagons, or any shape with a fixed number of sides.

To specify a constraint specific to Polygon, it is useful to introduce the concept of a linear ring:

  • 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.
  • A linear ring is the boundary of a surface or the boundary of a hole in a surface.
  • A linear ring must follow the right-hand rule with respect to the area it bounds, i.e., exterior rings are counterclockwise, and holes are clockwise.

The Polygon geometry type definition as follows:

  • For type Polygon, the coordinates member must be an array of linear ring coordinate arrays.
  • For Polygon with more than one of these rings, the first must be the exterior ring, and any others must be interior rings. The exterior ring bounds the surface, and the interior rings (if present) bound holes within the surface.
polygon = GeoJSON::Polygon.new([
  [[-10.0, -10.0], [10.0, -10.0], [10.0, 10.0], [-10.0,-10.0]],
  [[-1.0, -2.0], [3.0, -2.0], [3.0, 2.0], [-1.0,-2.0]]
])
polygon.to_json
{
  "type": "Polygon",
  "coordinates": [
    [
      [-10.0, -10.0],
      [10.0, -10.0],
      [10.0,10.0],
      [-10.0,-10.0]
    ],
    [
      [-1.0, -2.0],
      [3.0, -2.0],
      [3.0, 2.0],
      [-1.0,-2.0]
    ]
  ]
}

MultiPolygon

For type MultiPolygon, the coordinates member is an array of Polygon coordinate arrays.

polygon1 = GeoJSON::Polygon.new(
  [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]]
)
polygon2 = GeoJSON::Polygon.new(
  [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]]
)

multi_polygon = GeoJSON::MultiPolygon.new([polygon1, polygon2])
multi_polygon.to_json
{
  "type":"MultiPolygon",
  "coordinates":[
    [
      [
        [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]
      ]
    ]
  ]
}

GeometryCollection

A GeoJSON object with type GeometryCollection is a Geometry object.

A GeometryCollection has a member with the name geometries. The value of geometries is an array. Each element of this array is a GeoJSON Geometry object.

point = GeoJSON::Point.new([100.0, 0.0])
line_string = GeoJSON::LineString.new([
  [101.0, 0.0],
  [102.0, 1.0],
])
polygon = GeoJSON::Polygon.new([
  [
    [100.0, 0.0],
    [101.0, 0.0],
    [101.0, 1.0],
    [100.0, 1.0],
    [100.0, 0.0],
  ],
])

geometry_collection = GeoJSON::GeometryCollection.new([point, line_string, polygon])
geometry_collection.to_json
{
  "type":"GeometryCollection",
  "geometries":[
    {
      "type":"Point",
      "coordinates":[100.0,0.0]
    },
    {
      "type":"LineString",
      "coordinates":[
        [101.0,0.0],
        [102.0,1.0]
      ]
    },
    {
      "type":"Polygon",
      "coordinates":[
        [
          [100.0,0.0],
          [101.0,0.0],
          [101.0,1.0],
          [100.0,1.0],
          [100.0,0.0]
        ]
      ]
    }
  ]
}

Feature

A Feature object represents a spatially bounded thing. Every Feature object is a GeoJSON object no matter where it occurs in a GeoJSON text.

  • A Feature object has a "type" member with the value "Feature".
  • A Feature object has a member with the name "geometry". The value of the geometry member shall be either a Geometry object as defined above or, in the case that the Feature is unlocated, a JSON null value.
  • A Feature object has a member with the name "properties". The value of the properties member is an object (any JSON object or a JSON null value).
  • If a Feature has a commonly used identifier, that identifier should be included as a member of the Feature object with the name "id", and the value of this member is either a JSON string or number.
point = GeoJSON::Point.new([-80.1347334, 25.7663562])
properties = {"color" => "red"} of String => JSON::Any::Type
feature = GeoJSON::Feature.new(point, properties, id: 1)
feature.to_json
{
  "type":"Feature",
  "geometry":{
    "type":"Point",
    "coordinates":[-80.1347334,25.7663562]
  },
  "properties":{
    "color":"red"
  },
  "id":1
}

FeatureCollection

A GeoJSON object with the type "FeatureCollection" is a FeatureCollection object. A FeatureCollection object has a member with the name "features". The value of "features" is a JSON array. Each element of the array is a Feature object. It is possible for this array to be empty.

feature1 = GeoJSON::Feature.new(
  GeoJSON::Point.new([102.0, 0.5]),
  id: "point"
)

feature2 = GeoJSON::Feature.new(
  GeoJSON::Polygon.new([
    [
      [100.0, 0.0],
      [101.0, 0.0],
      [101.0, 1.0],
      [100.0, 1.0],
      [100.0, 0.0],
    ],
  ]),
  type: "polygon"
)

feature_collection = GeoJSON::FeatureCollection.new([feature1, feature2])
feature_collection.to_json
{
  "type":"FeatureCollection",
  "features":[
    {
      "type":"Feature",
      "geometry":{
        "type":"Point",
        "coordinates":[102.0,0.5]
      },
      "properties":null,
      "id":"point"
    },
    {
      "type":"Feature",
      "geometry":{
        "type":"Polygon",
        "coordinates":[
          [
            [100.0,0.0],
            [101.0,0.0],
            [101.0,1.0],
            [100.0,1.0],
            [100.0,0.0]
          ]
        ]
      },
      "properties":null,
      "id":"polygon"
    }
  ]
}

Foreign Members

For example, in the Point object shown below

{
  "type": "Point",
  "coordinates": [-80.1347334, 25.7663562],
  "title": "Example Point"
}

the name/value pair of "title": "Example Point" is a foreign member.

GeoJSON semantics do not apply to foreign members and their descendants, regardless of their names and values.

If the GeoJSON type include foreign members, this properties in the JSON document will be stored in a Hash(String, JSON::Any). On serialization, any keys inside json_unmapped will be serialized and appended to the current json object.

point = GeoJSON::Point.new([-80.1347334, 25.7663562])

json_unmapped = Hash(String, JSON::Any).new
json_unmapped["title"] = JSON::Any.new("Example Point")

point.json_unmapped = json_unmapped

Contributing

  1. Fork it (https://github.com/geocrystal/geojson/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors

License

Copyright: 2020-2024 Anton Maminov ([email protected])

This library is distributed under the MIT license. Please see the LICENSE file.